http-2 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ module HTTP2
5
5
  # Responsible for encoding header key-value pairs using HPACK algorithm.
6
6
  class Compressor
7
7
  include PackingExtensions
8
+ include BufferUtils
8
9
 
9
10
  # @param options [Hash] encoding options
10
11
  def initialize(options = {})
@@ -34,8 +35,8 @@ module HTTP2
34
35
  # @param buffer [String] buffer to pack bytes into
35
36
  # @param offset [Integer] offset to insert packed bytes in buffer
36
37
  # @return [String] binary string
37
- def integer(i, n, buffer:, offset: 0)
38
- limit = (2**n) - 1
38
+ def integer(i, n, buffer:, offset: buffer.size)
39
+ limit = (1 << n) - 1
39
40
  return pack([i], "C", buffer: buffer, offset: offset) if i < limit
40
41
 
41
42
  bytes = []
@@ -70,19 +71,23 @@ module HTTP2
70
71
  # :shorter Use Huffman when the result is strictly shorter
71
72
  #
72
73
  # @param str [String]
74
+ # @param buffer [String]
73
75
  # @return [String] binary string
74
- def string(str)
76
+ def string(str, buffer = "".b)
75
77
  case @cc.options[:huffman]
76
78
  when :always
77
- huffman_string(str)
79
+ huffman_string(str, buffer)
78
80
  when :never
79
- plain_string(str)
81
+ plain_string(str, buffer)
80
82
  else
81
- huffman = huffman_string(str)
82
-
83
- plain = plain_string(str)
84
-
85
- huffman.bytesize < plain.bytesize ? huffman : plain
83
+ huffman = Huffman.encode(str)
84
+ if huffman.bytesize < str.bytesize
85
+ huffman_offset = buffer.bytesize
86
+ append_str(buffer, huffman)
87
+ set_huffman_size(buffer, huffman_offset)
88
+ else
89
+ plain_string(str, buffer)
90
+ end
86
91
  end
87
92
  end
88
93
 
@@ -92,27 +97,30 @@ module HTTP2
92
97
  # @param buffer [String]
93
98
  # @return [Buffer]
94
99
  def header(h, buffer = "".b)
95
- rep = HEADREP[h[:type]]
100
+ type = h[:type]
101
+ rep = HEADREP[type]
102
+ offset = buffer.size
96
103
 
97
- case h[:type]
104
+ case type
98
105
  when :indexed
99
106
  integer(h[:name] + 1, rep[:prefix], buffer: buffer)
100
107
  when :changetablesize
101
108
  integer(h[:value], rep[:prefix], buffer: buffer)
102
109
  else
103
- if h[:name].is_a? Integer
104
- integer(h[:name] + 1, rep[:prefix], buffer: buffer)
110
+ name = h[:name]
111
+ if name.is_a? Integer
112
+ integer(name + 1, rep[:prefix], buffer: buffer)
105
113
  else
106
114
  integer(0, rep[:prefix], buffer: buffer)
107
- buffer << string(h[:name])
115
+ string(name, buffer)
108
116
  end
109
117
 
110
- buffer << string(h[:value])
118
+ string(h[:value], buffer)
111
119
  end
112
120
 
113
121
  # set header representation pattern on first byte
114
- fb = buffer.ord | rep[:pattern]
115
- buffer.setbyte(0, fb)
122
+ fb = buffer[offset].ord | rep[:pattern]
123
+ buffer.setbyte(offset, fb)
116
124
 
117
125
  buffer
118
126
  end
@@ -123,11 +131,10 @@ module HTTP2
123
131
  # @return [Buffer]
124
132
  def encode(headers)
125
133
  buffer = "".b
126
- pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ":" }
127
- headers = [*pseudo_headers, *regular_headers]
128
- commands = @cc.encode(headers)
129
- commands.each do |cmd|
130
- buffer << header(cmd)
134
+ headers.partition { |f, _| f.start_with? ":" }.each do |hs|
135
+ @cc.encode(hs) do |cmd|
136
+ header(cmd, buffer)
137
+ end
131
138
  end
132
139
 
133
140
  buffer
@@ -136,22 +143,31 @@ module HTTP2
136
143
  private
137
144
 
138
145
  # @param str [String]
146
+ # @param buffer [String]
139
147
  # @return [String] binary string
140
- def huffman_string(str)
141
- huffman = Huffman.new.encode(str)
142
- integer(huffman.bytesize, 7, buffer: huffman, offset: 0)
143
- huffman.setbyte(0, huffman.ord | 0x80)
144
- huffman
148
+ def huffman_string(str, buffer = "".b)
149
+ huffman_offset = buffer.bytesize
150
+ Huffman.encode(str, buffer)
151
+ set_huffman_size(buffer, huffman_offset)
145
152
  end
146
153
 
147
154
  # @param str [String]
155
+ # @param buffer [String]
148
156
  # @return [String] binary string
149
- def plain_string(str)
150
- plain = "".b
157
+ def plain_string(str, plain = "".b)
151
158
  integer(str.bytesize, 7, buffer: plain)
152
- plain << str.dup.force_encoding(Encoding::BINARY)
159
+ append_str(plain, str)
153
160
  plain
154
161
  end
162
+
163
+ # @param buffer [String]
164
+ # @param huffman_offset [Integer] buffer offset where huffman string was introduced
165
+ # @return [String] binary string
166
+ def set_huffman_size(buffer, huffman_offset)
167
+ integer(buffer.bytesize - huffman_offset, 7, buffer: buffer, offset: huffman_offset)
168
+ buffer.setbyte(huffman_offset, buffer[huffman_offset].ord | 0x80)
169
+ buffer
170
+ end
155
171
  end
156
172
  end
157
173
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module HTTP2
4
4
  module Header
5
- using StringExtensions
6
5
  # Responsible for decoding received headers and maintaining compression
7
6
  # context of the opposing peer. Decompressor must be initialized with
8
7
  # appropriate starting context based on local role: client or server.
@@ -12,6 +11,9 @@ module HTTP2
12
11
  # client_role = Decompressor.new(:response)
13
12
  class Decompressor
14
13
  include Error
14
+ include BufferUtils
15
+
16
+ FORBIDDEN_HEADERS = %w[connection te].freeze
15
17
 
16
18
  # @param options [Hash] decoding options. Only :table_size is effective.
17
19
  def initialize(options = {})
@@ -30,17 +32,23 @@ module HTTP2
30
32
  # @param n [Integer] number of available bits
31
33
  # @return [Integer]
32
34
  def integer(buf, n)
33
- limit = (2**n) - 1
34
- i = n.zero? ? 0 : (buf.shift_byte & limit)
35
+ limit = (1 << n) - 1
36
+ i = n.zero? ? 0 : (shift_byte(buf) & limit)
35
37
 
36
38
  m = 0
37
39
  if i == limit
38
- while (byte = buf.shift_byte)
40
+ offset = 0
41
+
42
+ buf.each_byte.with_index do |byte, idx|
43
+ offset = idx
44
+ # while (byte = shift_byte(buf))
39
45
  i += ((byte & 127) << m)
40
46
  m += 7
41
47
 
42
- break if (byte & 128).zero?
48
+ break if byte.nobits?(128)
43
49
  end
50
+
51
+ read_str(buf, offset + 1)
44
52
  end
45
53
 
46
54
  i
@@ -54,12 +62,12 @@ module HTTP2
54
62
  def string(buf)
55
63
  raise CompressionError, "invalid header block fragment" if buf.empty?
56
64
 
57
- huffman = (buf.getbyte(0) & 0x80) == 0x80
65
+ huffman = buf.getbyte(0).allbits?(0x80)
58
66
  len = integer(buf, 7)
59
- str = buf.read(len)
67
+ str = read_str(buf, len)
60
68
  raise CompressionError, "string too short" unless str.bytesize == len
61
69
 
62
- str = Huffman.new.decode(str) if huffman
70
+ str = Huffman.decode(str) if huffman
63
71
  str.force_encoding(Encoding::UTF_8)
64
72
  end
65
73
 
@@ -70,37 +78,36 @@ module HTTP2
70
78
  def header(buf)
71
79
  peek = buf.getbyte(0)
72
80
 
73
- header = {}
74
- header[:type], type = HEADREP.find do |_t, desc|
81
+ header_type, type = HEADREP.find do |_, desc|
75
82
  mask = (peek >> desc[:prefix]) << desc[:prefix]
76
83
  mask == desc[:pattern]
77
84
  end
78
85
 
79
- raise CompressionError unless header[:type]
86
+ raise CompressionError unless header_type && type
80
87
 
81
- header[:name] = integer(buf, type[:prefix])
88
+ header_name = integer(buf, type[:prefix])
82
89
 
83
- case header[:type]
90
+ case header_type
84
91
  when :indexed
85
- raise CompressionError if (header[:name]).zero?
92
+ raise CompressionError if header_name.zero?
93
+
94
+ header_name -= 1
86
95
 
87
- header[:name] -= 1
96
+ { type: header_type, name: header_name }
88
97
  when :changetablesize
89
- header[:value] = header[:name]
98
+ { type: header_type, name: header_name, value: header_name }
90
99
  else
91
- if (header[:name]).zero?
92
- header[:name] = string(buf)
100
+ if header_name.zero?
101
+ header_name = string(buf)
93
102
  else
94
- header[:name] -= 1
103
+ header_name -= 1
95
104
  end
96
- header[:value] = string(buf)
97
- end
105
+ header_value = string(buf)
98
106
 
99
- header
107
+ { type: header_type, name: header_name, value: header_value }
108
+ end
100
109
  end
101
110
 
102
- FORBIDDEN_HEADERS = %w[connection te].freeze
103
-
104
111
  # Decodes and processes header commands within provided buffer.
105
112
  #
106
113
  # @param buf [Buffer]
@@ -114,7 +121,7 @@ module HTTP2
114
121
  field, value = @cc.process(header(buf))
115
122
  next if field.nil?
116
123
 
117
- is_pseudo_header = field.start_with? ":"
124
+ is_pseudo_header = field.start_with?(":")
118
125
  if !decoding_pseudo_headers && is_pseudo_header
119
126
  raise ProtocolError, "one or more pseudo headers encountered after regular headers"
120
127
  end
@@ -75,15 +75,27 @@ module HTTP2
75
75
  ["vary", ""],
76
76
  ["via", ""],
77
77
  ["www-authenticate", ""]
78
- ].each { |pair| pair.each(&:freeze).freeze }.freeze
78
+ ].each(&:freeze).freeze
79
79
 
80
- STATIC_TABLE_BY_FIELD = STATIC_TABLE
81
- .each_with_object({})
82
- .with_index { |((field, value), hs), idx| (hs[field] ||= []) << [idx, value] }
83
- .each { |pair| pair.each(&:freeze).freeze }.freeze
80
+ STATIC_TABLE_BY_FIELD =
81
+ STATIC_TABLE
82
+ .each_with_object({})
83
+ .with_index { |((field, value), hs), idx| (hs[field] ||= []) << [idx, value].freeze }
84
+ .each_value(&:freeze)
85
+ .freeze
84
86
 
85
87
  STATIC_TABLE_SIZE = STATIC_TABLE.size
86
88
 
89
+ DEFAULT_OPTIONS = {
90
+ huffman: :shorter,
91
+ index: :all,
92
+ table_size: 4096
93
+ }.freeze
94
+
95
+ STATIC_ALL = %i[all static].freeze
96
+
97
+ STATIC_NEVER = %i[never static].freeze
98
+
87
99
  # Current table of header key-value pairs.
88
100
  attr_reader :table
89
101
 
@@ -94,6 +106,9 @@ module HTTP2
94
106
  # :index Symbol :all, :static, :never
95
107
  attr_reader :options
96
108
 
109
+ # Current table size in octets
110
+ attr_reader :current_table_size
111
+
97
112
  # Initializes compression context with appropriate client/server
98
113
  # defaults and maximum size of the dynamic table.
99
114
  #
@@ -102,15 +117,11 @@ module HTTP2
102
117
  # :huffman Symbol :always, :never, :shorter
103
118
  # :index Symbol :all, :static, :never
104
119
  def initialize(options = {})
105
- default_options = {
106
- huffman: :shorter,
107
- index: :all,
108
- table_size: 4096
109
- }
110
120
  @table = []
111
- @options = default_options.merge(options)
121
+ @options = DEFAULT_OPTIONS.merge(options)
112
122
  @limit = @options[:table_size]
113
123
  @_table_updated = false
124
+ @current_table_size = 0
114
125
  end
115
126
 
116
127
  # Duplicates current compression context
@@ -138,10 +149,13 @@ module HTTP2
138
149
  # @return [Array] +[key, value]+
139
150
  def dereference(index)
140
151
  # NOTE: index is zero-based in this module.
141
- value = STATIC_TABLE[index] || @table[index - STATIC_TABLE_SIZE]
142
- raise CompressionError, "Index too large" unless value
152
+ return STATIC_TABLE[index] if index < STATIC_TABLE_SIZE
153
+
154
+ idx = index - STATIC_TABLE_SIZE
155
+
156
+ raise CompressionError, "Index too large" if idx >= @table.size
143
157
 
144
- value
158
+ @table[index - STATIC_TABLE_SIZE]
145
159
  end
146
160
 
147
161
  # Header Block Processing
@@ -151,9 +165,11 @@ module HTTP2
151
165
  # @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
152
166
  # or nil if +cmd[:type]+ is +:changetablesize+
153
167
  def process(cmd)
154
- emit = nil
168
+ type = cmd[:type]
169
+ name = cmd[:name]
170
+ value = cmd[:value]
155
171
 
156
- case cmd[:type]
172
+ case type
157
173
  when :changetablesize
158
174
  raise CompressionError, "tried to change table size after adding elements to table" if @_table_updated
159
175
 
@@ -161,21 +177,18 @@ module HTTP2
161
177
  # we should blow up if we receive another frame where the new table size is bigger.
162
178
  table_size_updated = @limit != @options[:table_size]
163
179
 
164
- raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && cmd[:value] > @limit
180
+ raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && value > @limit
165
181
 
166
- self.table_size = cmd[:value]
182
+ self.table_size = value
167
183
 
184
+ nil
168
185
  when :indexed
169
186
  # Indexed Representation
170
187
  # An _indexed representation_ entails the following actions:
171
188
  # o The header field corresponding to the referenced entry in either
172
189
  # the static table or dynamic table is added to the decoded header
173
190
  # list.
174
- idx = cmd[:name]
175
-
176
- k, v = dereference(idx)
177
- emit = [k, v]
178
-
191
+ dereference(name)
179
192
  when :incremental, :noindex, :neverindexed
180
193
  # A _literal representation_ that is _not added_ to the dynamic table
181
194
  # entails the following action:
@@ -186,27 +199,28 @@ module HTTP2
186
199
  # o The header field is added to the decoded header list.
187
200
  # o The header field is inserted at the beginning of the dynamic table.
188
201
 
189
- case cmd[:name]
202
+ case name
190
203
  when Integer
191
- k, v = dereference(cmd[:name])
204
+ name, v = dereference(name)
192
205
 
193
- cmd = cmd.dup
194
- cmd[:index] ||= cmd[:name]
195
- cmd[:value] ||= v
196
- cmd[:name] = k
206
+ value ||= v
197
207
  when UPPER
198
- raise ProtocolError, "Invalid uppercase key: #{cmd[:name]}"
208
+ raise ProtocolError, "Invalid uppercase key: #{name}"
199
209
  end
200
210
 
201
- emit = [cmd[:name], cmd[:value]]
211
+ emit = [name, value]
202
212
 
203
- add_to_table(emit) if cmd[:type] == :incremental
213
+ # add to table
214
+ if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
215
+ @table.unshift(emit)
216
+ @current_table_size += name.bytesize + value.bytesize + 32
217
+ @_table_updated = true
218
+ end
204
219
 
220
+ emit
205
221
  else
206
- raise CompressionError, "Invalid type: #{cmd[:type]}"
222
+ raise CompressionError, "Invalid type: #{type}"
207
223
  end
208
-
209
- emit
210
224
  end
211
225
 
212
226
  # Plan header compression according to +@options [:index]+
@@ -217,9 +231,9 @@ module HTTP2
217
231
  # @param headers [Array] +[[name, value], ...]+
218
232
  # @return [Array] array of commands
219
233
  def encode(headers)
220
- commands = []
221
234
  # Literals commands are marked with :noindex when index is not used
222
- noindex = %i[static never].include?(@options[:index])
235
+ noindex = STATIC_NEVER.include?(@options[:index])
236
+
223
237
  headers.each do |field, value|
224
238
  # Literal header names MUST be translated to lowercase before
225
239
  # encoding and transmission.
@@ -227,10 +241,9 @@ module HTTP2
227
241
  value = "/" if field == ":path" && value.empty?
228
242
  cmd = addcmd(field, value)
229
243
  cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
230
- commands << cmd
231
244
  process(cmd)
245
+ yield cmd
232
246
  end
233
- commands
234
247
  end
235
248
 
236
249
  # Emits command for a header.
@@ -243,30 +256,36 @@ module HTTP2
243
256
  # :static Use static table only.
244
257
  # :all Use all of them.
245
258
  #
246
- # @param header [Array] +[name, value]+
259
+ # @param field [String] the header field
260
+ # @param value [String] the header value
247
261
  # @return [Hash] command
248
- def addcmd(*header)
262
+ def addcmd(field, value)
263
+ # @type var exact: Integer?
249
264
  exact = nil
265
+ # @type var name_only: Integer?
250
266
  name_only = nil
251
267
 
252
- if %i[all static].include?(@options[:index])
253
- field, value = header
254
- if (svalues = STATIC_TABLE_BY_FIELD[field])
255
- svalues.each do |i, svalue|
256
- name_only ||= i
257
- if svalue == value
258
- exact = i
259
- break
260
- end
268
+ index_type = @options[:index]
269
+
270
+ if STATIC_ALL.include?(index_type) &&
271
+ STATIC_TABLE_BY_FIELD.key?(field)
272
+ STATIC_TABLE_BY_FIELD[field].each do |i, svalue|
273
+ name_only ||= i
274
+ if value == svalue
275
+ exact = i
276
+ break
261
277
  end
262
278
  end
263
279
  end
264
- if [:all].include?(@options[:index]) && !exact
265
- @table.each_index do |i|
266
- if @table[i] == header
267
- exact ||= i + STATIC_TABLE_SIZE
280
+
281
+ if index_type == :all && !exact
282
+ @table.each_with_index do |(hfield, hvalue), i|
283
+ next unless field == hfield
284
+
285
+ if value == hvalue
286
+ exact = i + STATIC_TABLE_SIZE
268
287
  break
269
- elsif @table[i].first == header.first
288
+ else
270
289
  name_only ||= i + STATIC_TABLE_SIZE
271
290
  end
272
291
  end
@@ -274,10 +293,8 @@ module HTTP2
274
293
 
275
294
  if exact
276
295
  { name: exact, type: :indexed }
277
- elsif name_only
278
- { name: name_only, value: header.last, type: :incremental }
279
296
  else
280
- { name: header.first, value: header.last, type: :incremental }
297
+ { name: name_only || field, value: value, type: :incremental }
281
298
  end
282
299
  end
283
300
 
@@ -285,13 +302,7 @@ module HTTP2
285
302
  # When the size is reduced, some headers might be evicted.
286
303
  def table_size=(size)
287
304
  @limit = size
288
- size_check(nil)
289
- end
290
-
291
- # Returns current table size in octets
292
- # @return [Integer]
293
- def current_table_size
294
- @table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
305
+ size_check(0)
295
306
  end
296
307
 
297
308
  def listen_on_table
@@ -302,32 +313,20 @@ module HTTP2
302
313
 
303
314
  private
304
315
 
305
- # Add a name-value pair to the dynamic table.
306
- # Older entries might have been evicted so that
307
- # the new entry fits in the dynamic table.
308
- #
309
- # @param cmd [Array] +[name, value]+
310
- def add_to_table(cmd)
311
- return unless size_check(cmd)
312
-
313
- @table.unshift(cmd)
314
- @_table_updated = true
315
- end
316
-
317
316
  # To keep the dynamic table size lower than or equal to @limit,
318
317
  # remove one or more entries at the end of the dynamic table.
319
318
  #
320
- # @param cmd [Hash]
319
+ # @param cmdsize [Integer]
321
320
  # @return [Boolean] whether +cmd+ fits in the dynamic table.
322
- def size_check(cmd)
323
- cursize = current_table_size
324
- cmdsize = cmd.nil? ? 0 : cmd[0].bytesize + cmd[1].bytesize + 32
321
+ def size_check(cmdsize)
322
+ unless @table.empty?
323
+ while @current_table_size + cmdsize > @limit
325
324
 
326
- while cursize + cmdsize > @limit
327
- break if @table.empty?
325
+ name, value = @table.pop
326
+ @current_table_size -= name.bytesize + value.bytesize + 32
327
+ break if @table.empty?
328
328
 
329
- e = @table.pop
330
- cursize -= e[0].bytesize + e[1].bytesize + 32
329
+ end
331
330
  end
332
331
 
333
332
  cmdsize <= @limit
@@ -9,11 +9,12 @@ module HTTP2
9
9
  # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
10
10
  module Header
11
11
  # Huffman encoder/decoder
12
- class Huffman
13
- using StringExtensions
12
+ module Huffman
13
+ module_function
14
14
 
15
15
  include Error
16
- include PackingExtensions
16
+ extend PackingExtensions
17
+ extend BufferUtils
17
18
 
18
19
  BITS_AT_ONCE = 4
19
20
  EOS = 256
@@ -23,11 +24,13 @@ module HTTP2
23
24
  # Length is not encoded in this method.
24
25
  #
25
26
  # @param str [String]
27
+ # @param buffer [String]
26
28
  # @return [String] binary string
27
- def encode(str)
28
- bitstring = str.each_byte.map { |chr| ENCODE_TABLE[chr] }.join
29
- bitstring << ("1" * ((8 - bitstring.size) % 8))
30
- [bitstring].pack("B*")
29
+ def encode(str, buffer = "".b)
30
+ bitstring = String.new("", encoding: Encoding::BINARY, capacity: (str.bytesize * 30) + ((8 - str.size) % 8))
31
+ str.each_byte { |chr| append_str(bitstring, ENCODE_TABLE[chr]) }
32
+ append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
33
+ pack([bitstring], "B*", buffer: buffer)
31
34
  end
32
35
 
33
36
  # Decodes provided Huffman coded string.
@@ -48,11 +51,10 @@ module HTTP2
48
51
  # Each transition is [emit, next]
49
52
  # [emit] character to be emitted on this transition, empty string, or EOS.
50
53
  # [next] next state number.
51
- trans = MACHINE[state][branch]
52
- raise CompressionError, "Huffman decode error (EOS found)" if trans.first == EOS
54
+ first, state = MACHINE.dig(state, branch)
55
+ raise CompressionError, "Huffman decode error (EOS found)" if first == EOS
53
56
 
54
- emit << trans.first.chr if trans.first
55
- state = trans.last
57
+ append_str(emit, first.chr) if first
56
58
  end
57
59
  end
58
60
  # Check whether partial input is correctly filled
@@ -8,7 +8,7 @@
8
8
 
9
9
  module HTTP2
10
10
  module Header
11
- class Huffman
11
+ module Huffman
12
12
  # :nodoc:
13
13
  MAX_FINAL_STATE = 7
14
14
  MACHINE = [
@@ -268,7 +268,7 @@ module HTTP2
268
268
  [[28, 29], [28, 5], [29, 29], [29, 5], [30, 29], [30, 5], [31, 29], [31, 5], [127, 29], [127, 5], [220, 29], [220, 5], [249, 29], [249, 5], [nil, 254], [nil, 255]],
269
269
  [[10, 17], [10, 18], [10, 19], [10, 20], [10, 21], [10, 22], [10, 23], [10, 7], [13, 17], [13, 18], [13, 19], [13, 20], [13, 21], [13, 22], [13, 23], [13, 7]],
270
270
  [[22, 17], [22, 18], [22, 19], [22, 20], [22, 21], [22, 22], [22, 23], [22, 7], [256, 17], [256, 18], [256, 19], [256, 20], [256, 21], [256, 22], [256, 23], [256, 7]],
271
- ].each { |arr| arr.each { |subarr| subarr.each(&:freeze) }.freeze }.freeze
271
+ ].each { |arr| arr.each { |subarr| subarr.freeze }.freeze }.freeze
272
272
  end
273
273
  end
274
274
  end
data/lib/http/2/server.rb CHANGED
@@ -79,7 +79,7 @@ module HTTP2
79
79
 
80
80
  # Process received HTTP2-Settings payload
81
81
  buf = "".b
82
- buf << Base64.urlsafe_decode64(settings.to_s)
82
+ append_str(buf, Base64.urlsafe_decode64(settings.to_s))
83
83
  @framer.common_header(
84
84
  {
85
85
  length: buf.bytesize,
@@ -120,6 +120,12 @@ module HTTP2
120
120
  @state = :waiting_magic
121
121
  end
122
122
 
123
+ def activate_stream(**)
124
+ super.tap do |stream|
125
+ stream.on(:promise, &method(:promise))
126
+ end
127
+ end
128
+
123
129
  def origin_set=(origins)
124
130
  @origin_set = Array(origins).map(&:to_s)
125
131
  @origins_sent = @origin_set.empty?