Class: HTTP2::Header::Compressor

Inherits:
Object
  • Object
show all
Includes:
BufferUtils, PackingExtensions
Defined in:
lib/http/2/header/compressor.rb,
sig/header/compressor.rbs

Overview

Responsible for encoding header key-value pairs using HPACK algorithm.

Instance Method Summary collapse

Methods included from BufferUtils

#append_str, #read_str, #read_uint32, #shift_byte

Methods included from PackingExtensions

#pack

Constructor Details

#initialize(settings = Settings.new) ⇒ Compressor

Returns a new instance of Compressor.

Parameters:

  • settings (Settings) (defaults to: Settings.new)


10
11
12
# File 'lib/http/2/header/compressor.rb', line 10

def initialize(settings = Settings.new)
  @cc = EncodingContext.new(settings)
end

Instance Method Details

#encode(headers) ⇒ String

Encodes provided list of HTTP headers.

Parameters:

  • (Enumerable[header_pair])

Returns:

  • (String)


112
113
114
115
116
117
118
119
120
121
# File 'lib/http/2/header/compressor.rb', line 112

def encode(headers)
  buffer = "".b
  headers.partition { |f, _| f.start_with? ":" }.each do |hs|
    @cc.encode(hs) do |cmd|
      header(cmd, buffer)
    end
  end

  buffer
end

#header(h, buffer = "".b) ⇒ String

Encodes h header command with appropriate header representation into buffer.

Parameters:

  • (header_command)
  • (String)

Returns:

  • (String)


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/http/2/header/compressor.rb', line 82

def header(h, buffer = "".b)
  type = h[:type]
  rep = HEADREP[type]
  offset = buffer.size

  case type
  when :indexed
    integer(h[:name] + 1, rep[:prefix], buffer: buffer)
  when :changetablesize
    integer(h[:value], rep[:prefix], buffer: buffer)
  else
    name = h[:name]
    if name.is_a? Integer
      integer(name + 1, rep[:prefix], buffer: buffer)
    else
      integer(0, rep[:prefix], buffer: buffer)
      string(name, buffer)
    end

    string(h[:value], buffer)
  end

  # set header representation pattern on first byte
  fb = buffer.getbyte(offset) | rep[:pattern]
  buffer.setbyte(offset, fb)

  buffer
end

#huffman_string(str, buffer = "".b) ⇒ String

encodes str into buffer using Huffman encoding.

Parameters:

  • str (String)
  • buffer (String) (defaults to: "".b)

Returns:

  • (String)


126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/http/2/header/compressor.rb', line 126

def huffman_string(str, buffer = "".b)
  huffman_offset = buffer.bytesize
  buffer << "\x00".b
  Huffman.encode(str, buffer)
  size = buffer.bytesize - huffman_offset - 1

  if size < 127
    buffer.setbyte(huffman_offset, 0x80 | size)
  else
    buffer.slice!(huffman_offset, 1)
    set_huffman_size(buffer, huffman_offset)
  end
  buffer
end

#integer(i, n, buffer:, offset: buffer.size) ⇒ String

Encodes i via integer representation into buffer at the offset set by offset.

If I < 2^N - 1, encode I on N bits Else encode 2^N - 1 on N bits I = I - (2^N - 1) While I >= 128 Encode (I % 128 + 128) on 8 bits I = I / 128 encode (I) on 8 bits

Parameters:

  • (Integer)
  • (Integer)
  • buffer: (String)
  • offset: (Integer) (defaults to: buffer.size)

Returns:

  • (String)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/http/2/header/compressor.rb', line 31

def integer(i, n, buffer:, offset: buffer.size)
  limit = (1 << n) - 1
  return pack([i], "C", buffer: buffer, offset: offset) if i < limit

  bytes = []
  bytes.push limit unless n.zero?

  i -= limit
  while i >= 128
    bytes.push((i % 128) + 128)
    i /= 128
  end

  bytes.push i
  pack(bytes, "C*", buffer: buffer, offset: offset)
end

#plain_string(str, plain = "".b) ⇒ String

encodes str into buffer.

Parameters:

  • str (String)
  • buffer (String)

Returns:

  • (String)


142
143
144
145
146
# File 'lib/http/2/header/compressor.rb', line 142

def plain_string(str, plain = "".b)
  integer(str.bytesize, 7, buffer: plain)
  append_str(plain, str)
  plain
end

#set_huffman_size(buffer, huffman_offset) ⇒ String

encodes the huffman string size from buffer into the string at the offset indicated by huffman_offset

Parameters:

  • str (String)
  • huffman_offset (Integer)

Returns:

  • (String)


149
150
151
152
153
# File 'lib/http/2/header/compressor.rb', line 149

def set_huffman_size(buffer, huffman_offset)
  integer(buffer.bytesize - huffman_offset, 7, buffer: buffer, offset: huffman_offset)
  buffer.setbyte(huffman_offset, buffer.getbyte(huffman_offset) | 0x80)
  buffer
end

#string(str, buffer = "".b) ⇒ String

Encodes provided value via string literal representation.

  • The string length, defined as the number of bytes needed to store its UTF-8 representation, is represented as an integer with a seven bits prefix. If the string length is strictly less than 127, it is represented as one byte.
  • If the bit 7 of the first byte is 1, the string value is represented as a list of Huffman encoded octets (padded with bit 1's until next octet boundary).
  • If the bit 7 of the first byte is 0, the string value is represented as a list of UTF-8 encoded octets.

Parameters:

  • (String)
  • buffer (String) (defaults to: "".b)

Returns:

  • (String)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/http/2/header/compressor.rb', line 61

def string(str, buffer = "".b)
  case @cc.settings.huffman
  when :always
    huffman_string(str, buffer)
  when :never
    plain_string(str, buffer)
  else
    huffman = Huffman.encode(str)
    if huffman.bytesize < str.bytesize
      huffman_offset = buffer.bytesize
      integer(huffman.bytesize, 7, buffer: buffer)
      buffer.setbyte(huffman_offset, buffer.getbyte(huffman_offset) | 0x80)
      append_str(buffer, huffman)
      buffer
    else
      plain_string(str, buffer)
    end
  end
end

#table_size=(size) ⇒ void

This method returns an undefined value.

Set dynamic table size in EncodingContext

Parameters:

  • (Integer)


15
16
17
# File 'lib/http/2/header/compressor.rb', line 15

def table_size=(size)
  @cc.table_size = size
end