http-2 1.0.2 → 1.1.1

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.
@@ -7,6 +7,8 @@ module HTTP2
7
7
  module FlowBuffer
8
8
  include Error
9
9
 
10
+ attr_reader :send_buffer
11
+
10
12
  MAX_WINDOW_SIZE = (2 << 30) - 1
11
13
 
12
14
  # Amount of buffered data. Only DATA payloads are subject to flow stream
@@ -14,7 +16,7 @@ module HTTP2
14
16
  #
15
17
  # @return [Integer]
16
18
  def buffered_amount
17
- send_buffer.bytesize
19
+ @send_buffer.bytesize
18
20
  end
19
21
 
20
22
  def flush
@@ -23,13 +25,9 @@ module HTTP2
23
25
 
24
26
  private
25
27
 
26
- def send_buffer
27
- @send_buffer ||= FrameBuffer.new
28
- end
29
-
30
28
  def update_local_window(frame)
31
29
  frame_size = frame[:payload].bytesize
32
- frame_size += frame[:padding] || 0
30
+ frame_size += frame.fetch(:padding, 0)
33
31
  @local_window -= frame_size
34
32
  end
35
33
 
@@ -69,31 +67,47 @@ module HTTP2
69
67
  # @param frame [Hash]
70
68
  # @param encode [Boolean] set to true by connection
71
69
  def send_data(frame = nil, encode = false)
72
- send_buffer << frame unless frame.nil?
70
+ if frame
71
+ if @send_buffer.empty?
72
+ frame_size = frame[:payload].bytesize
73
+ end_stream = frame[:flags].include?(:end_stream)
74
+ # if buffer is empty, and frame is either end 0 length OR
75
+ # is within available window size, skip buffering and send immediately.
76
+ if @remote_window.positive?
77
+ return send_frame(frame, encode) if frame_size <= @remote_window
78
+ elsif frame_size.zero? && end_stream
79
+ return send_frame(frame, encode)
80
+ end
81
+ end
73
82
 
74
- while (frame = send_buffer.retrieve(@remote_window))
83
+ @send_buffer << frame
84
+ end
75
85
 
76
- # puts "#{self.class} -> #{@remote_window}"
77
- sent = frame[:payload].bytesize
86
+ while (frame = @send_buffer.retrieve(@remote_window))
87
+ send_frame(frame, encode)
88
+ end
89
+ end
78
90
 
79
- manage_state(frame) do
80
- if encode
81
- encode(frame).each { |f| emit(:frame, f) }
82
- else
83
- emit(:frame, frame)
84
- end
85
- @remote_window -= sent
91
+ def send_frame(frame, encode)
92
+ sent = frame[:payload].bytesize
93
+
94
+ manage_state(frame) do
95
+ if encode
96
+ encode(frame)
97
+ else
98
+ emit(:frame, frame)
86
99
  end
100
+ @remote_window -= sent
87
101
  end
88
102
  end
89
103
 
90
104
  def process_window_update(frame:, encode: false)
91
105
  return if frame[:ignore]
92
106
 
93
- if frame[:increment]
94
- raise ProtocolError, "increment MUST be higher than zero" if frame[:increment].zero?
107
+ if (increment = frame[:increment])
108
+ raise ProtocolError, "increment MUST be higher than zero" if increment.zero?
95
109
 
96
- @remote_window += frame[:increment]
110
+ @remote_window += increment
97
111
  error(:flow_control_error, msg: "window size too large") if @remote_window > MAX_WINDOW_SIZE
98
112
  end
99
113
  send_data(nil, encode)
@@ -114,45 +128,41 @@ module HTTP2
114
128
  end
115
129
 
116
130
  def empty?
117
- @bytesize.zero?
131
+ @buffer.empty?
118
132
  end
119
133
 
120
134
  def retrieve(window_size)
121
135
  frame = @buffer.first or return
122
136
 
123
137
  frame_size = frame[:payload].bytesize
124
- end_stream = frame[:flags].include? :end_stream
138
+ end_stream = frame[:flags].include?(:end_stream)
125
139
 
126
140
  # Frames with zero length with the END_STREAM flag set (that
127
141
  # is, an empty DATA frame) MAY be sent if there is no available space
128
142
  # in either flow control window.
129
- return if window_size <= 0 && !(frame_size == 0 && end_stream)
130
-
131
- @buffer.shift
143
+ return if window_size <= 0 && !(frame_size.zero? && end_stream)
132
144
 
133
145
  if frame_size > window_size
134
- payload = frame[:payload]
135
146
  chunk = frame.dup
147
+ payload = frame[:payload]
136
148
 
137
149
  # Split frame so that it fits in the window
138
150
  # TODO: consider padding!
139
- frame_bytes = payload.byteslice(0, window_size)
140
- payload = payload.byteslice(window_size..-1)
141
151
 
142
- frame[:payload] = frame_bytes
143
- frame[:length] = frame_bytes.bytesize
144
- chunk[:payload] = payload
145
- chunk[:length] = payload.bytesize
152
+ chunk[:payload] = payload.byteslice(0, window_size)
153
+ chunk[:length] = window_size
154
+ frame[:payload] = payload.byteslice(window_size..-1)
155
+ frame[:length] = frame_size - window_size
146
156
 
147
157
  # if no longer last frame in sequence...
148
- frame[:flags] -= [:end_stream] if end_stream
158
+ chunk[:flags] -= [:end_stream] if end_stream
149
159
 
150
- @buffer.unshift(chunk)
151
160
  @bytesize -= window_size
161
+ chunk
152
162
  else
153
163
  @bytesize -= frame_size
164
+ @buffer.shift
154
165
  end
155
- frame
156
166
  end
157
167
  end
158
168
  end
data/lib/http/2/framer.rb CHANGED
@@ -36,6 +36,8 @@ module HTTP2
36
36
  origin: 0xc
37
37
  }.freeze
38
38
 
39
+ FRAME_TYPES_BY_NAME = FRAME_TYPES.invert.freeze
40
+
39
41
  FRAME_TYPES_WITH_PADDING = %i[data headers push_promise].freeze
40
42
 
41
43
  # Per frame flags as defined by the spec
@@ -126,30 +128,46 @@ module HTTP2
126
128
  # @param buffer [String] buffer to pack bytes into
127
129
  # @return [String]
128
130
  def common_header(frame, buffer:)
129
- raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
131
+ type = frame[:type]
132
+
133
+ raise CompressionError, "Invalid frame type (#{type})" unless FRAME_TYPES[type]
134
+
135
+ length = frame[:length]
136
+
137
+ raise CompressionError, "Frame size is too large: #{length}" if length > @remote_max_frame_size
130
138
 
131
- raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @remote_max_frame_size
139
+ raise CompressionError, "Frame size is invalid: #{length}" if length < 0
132
140
 
133
- raise CompressionError, "Frame size is invalid: #{frame[:length]}" if frame[:length] < 0
141
+ stream_id = frame.fetch(:stream, 0)
134
142
 
135
- raise CompressionError, "Stream ID (#{frame[:stream]}) is too large" if frame[:stream] > MAX_STREAM_ID
143
+ raise CompressionError, "Stream ID (#{stream_id}) is too large" if stream_id > MAX_STREAM_ID
136
144
 
137
- if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
145
+ if type == :window_update && frame[:increment] > MAX_WINDOWINC
138
146
  raise CompressionError, "Window increment (#{frame[:increment]}) is too large"
139
147
  end
140
148
 
149
+ header = buffer
150
+
151
+ # make sure the buffer is binary and unfrozen
152
+ if buffer.frozen?
153
+ header = String.new("", encoding: Encoding::BINARY, capacity: buffer.bytesize + 9) # header length
154
+ append_str(header, buffer)
155
+ else
156
+ header.force_encoding(Encoding::BINARY)
157
+ end
158
+
141
159
  pack([
142
- (frame[:length] >> FRAME_LENGTH_HISHIFT),
143
- (frame[:length] & FRAME_LENGTH_LOMASK),
144
- FRAME_TYPES[frame[:type]],
160
+ (length >> FRAME_LENGTH_HISHIFT),
161
+ (length & FRAME_LENGTH_LOMASK),
162
+ FRAME_TYPES[type],
145
163
  frame[:flags].reduce(0) do |acc, f|
146
- position = FRAME_FLAGS[frame[:type]][f]
147
- raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
164
+ position = FRAME_FLAGS[type][f]
165
+ raise CompressionError, "Invalid frame flag (#{f}) for #{type}" unless position
148
166
 
149
167
  acc | (1 << position)
150
168
  end,
151
- frame[:stream]
152
- ], HEADERPACK, buffer: buffer, offset: 0) # 8+16,8,8,32
169
+ stream_id
170
+ ], HEADERPACK, buffer: header, offset: 0) # 8+16,8,8,32
153
171
  end
154
172
 
155
173
  # Decodes common 9-byte header.
@@ -157,19 +175,21 @@ module HTTP2
157
175
  # @param buf [Buffer]
158
176
  # @return [Hash] the corresponding frame
159
177
  def read_common_header(buf)
160
- frame = {}
161
178
  len_hi, len_lo, type, flags, stream = buf.byteslice(0, 9).unpack(HEADERPACK)
162
179
 
163
- frame[:length] = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
164
- frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
165
- if frame[:type]
166
- frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc|
167
- acc << name if flags.anybits?((1 << pos))
168
- end
169
- end
180
+ type = FRAME_TYPES_BY_NAME[type]
181
+ length = (len_hi << FRAME_LENGTH_HISHIFT) | len_lo
170
182
 
171
- frame[:stream] = stream & RBIT
172
- frame
183
+ return { length: length } unless type
184
+
185
+ {
186
+ type: type,
187
+ flags: FRAME_FLAGS[type].filter_map do |name, pos|
188
+ name if flags.anybits?((1 << pos))
189
+ end,
190
+ length: length,
191
+ stream: stream & RBIT
192
+ }
173
193
  end
174
194
 
175
195
  # Generates encoded HTTP/2 frame.
@@ -177,53 +197,60 @@ module HTTP2
177
197
  #
178
198
  # @param frame [Hash]
179
199
  def generate(frame)
180
- bytes = "".b
181
200
  length = 0
182
-
183
- frame[:flags] ||= []
184
- frame[:stream] ||= 0
201
+ frame[:flags] ||= EMPTY
185
202
 
186
203
  case frame[:type]
187
- when :data
188
- bytes << frame[:payload]
189
- bytes.force_encoding(Encoding::BINARY)
190
- length += frame[:payload].bytesize
204
+ when :data, :continuation
205
+ bytes = frame[:payload]
206
+ length = bytes.bytesize
191
207
 
192
208
  when :headers
209
+ headers = frame[:payload]
210
+
193
211
  if frame[:weight] || frame[:dependency] || !frame[:exclusive].nil?
194
212
  unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
195
213
  raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
196
214
  end
197
215
 
198
- frame[:flags] += [:priority] unless frame[:flags].include? :priority
216
+ frame[:flags] += [:priority] unless frame[:flags].include?(:priority)
199
217
  end
200
218
 
201
- if frame[:flags].include? :priority
219
+ if frame[:flags].include?(:priority)
220
+ length = 5 + headers.bytesize
221
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
202
222
  pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
203
223
  pack([frame[:weight] - 1], UINT8, buffer: bytes)
204
- length += 5
224
+ append_str(bytes, headers)
225
+ else
226
+ length = headers.bytesize
227
+ bytes = headers
205
228
  end
206
229
 
207
- bytes << frame[:payload]
208
- length += frame[:payload].bytesize
209
-
210
230
  when :priority
211
231
  unless frame[:weight] && frame[:dependency] && !frame[:exclusive].nil?
212
232
  raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
213
233
  end
214
234
 
235
+ length = 5
236
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
215
237
  pack([(frame[:exclusive] ? EBIT : 0) | (frame[:dependency] & RBIT)], UINT32, buffer: bytes)
216
238
  pack([frame[:weight] - 1], UINT8, buffer: bytes)
217
- length += 5
218
239
 
219
240
  when :rst_stream
241
+ length = 4
242
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
220
243
  pack_error(frame[:error], buffer: bytes)
221
- length += 4
222
244
 
223
245
  when :settings
224
- raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
246
+ if (stream_id = frame[:stream]) && stream_id.nonzero?
247
+ raise CompressionError, "Invalid stream ID (#{stream_id})"
248
+ end
225
249
 
226
- frame[:payload].each do |(k, v)|
250
+ settings = frame[:payload]
251
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
252
+
253
+ settings.each do |(k, v)|
227
254
  if k.is_a? Integer # rubocop:disable Style/GuardClause
228
255
  DEFINED_SETTINGS.value?(k) || next
229
256
  else
@@ -238,42 +265,42 @@ module HTTP2
238
265
  end
239
266
 
240
267
  when :push_promise
268
+ length = 4 + frame[:payload].bytesize
269
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
241
270
  pack([frame[:promise_stream] & RBIT], UINT32, buffer: bytes)
242
- bytes << frame[:payload]
243
- length += 4 + frame[:payload].bytesize
271
+ append_str(bytes, frame[:payload])
244
272
 
245
273
  when :ping
246
- raise CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)" if frame[:payload].bytesize != 8
274
+ bytes = frame[:payload].b
275
+ raise CompressionError, "Invalid payload size (#{bytes.size} != 8 bytes)" if bytes.bytesize != 8
247
276
 
248
- bytes << frame[:payload]
249
- length += 8
277
+ length = 8
250
278
 
251
279
  when :goaway
280
+ data = frame[:payload]
281
+ length = 8
282
+ length += data.bytesize if data
283
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
284
+
252
285
  pack([frame[:last_stream] & RBIT], UINT32, buffer: bytes)
253
286
  pack_error(frame[:error], buffer: bytes)
254
- length += 8
255
287
 
256
- if frame[:payload]
257
- bytes << frame[:payload]
258
- length += frame[:payload].bytesize
259
- end
288
+ append_str(bytes, data) if data
260
289
 
261
290
  when :window_update
291
+ length = 4
292
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
262
293
  pack([frame[:increment] & RBIT], UINT32, buffer: bytes)
263
- length += 4
264
-
265
- when :continuation
266
- bytes << frame[:payload]
267
- length += frame[:payload].bytesize
268
294
 
269
295
  when :altsvc
296
+ length = 6
297
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
270
298
  pack([frame[:max_age], frame[:port]], UINT32 + UINT16, buffer: bytes)
271
- length += 6
272
299
  if frame[:proto]
273
300
  raise CompressionError, "Proto too long" if frame[:proto].bytesize > 255
274
301
 
275
302
  pack([frame[:proto].bytesize], UINT8, buffer: bytes)
276
- bytes << frame[:proto]
303
+ append_str(bytes, frame[:proto])
277
304
  length += 1 + frame[:proto].bytesize
278
305
  else
279
306
  pack([0], UINT8, buffer: bytes)
@@ -283,24 +310,25 @@ module HTTP2
283
310
  raise CompressionError, "Host too long" if frame[:host].bytesize > 255
284
311
 
285
312
  pack([frame[:host].bytesize], UINT8, buffer: bytes)
286
- bytes << frame[:host]
313
+ append_str(bytes, frame[:host])
287
314
  length += 1 + frame[:host].bytesize
288
315
  else
289
316
  pack([0], UINT8, buffer: bytes)
290
317
  length += 1
291
318
  end
292
319
  if frame[:origin]
293
- bytes << frame[:origin]
320
+ append_str(bytes, frame[:origin])
294
321
  length += frame[:origin].bytesize
295
322
  end
296
323
 
297
324
  when :origin
298
- frame[:payload].each do |origin|
325
+ origins = frame[:payload]
326
+ length = origins.sum(&:bytesize) + (2 * origins.size)
327
+ bytes = String.new("", encoding: Encoding::BINARY, capacity: length)
328
+ origins.each do |origin|
299
329
  pack([origin.bytesize], UINT16, buffer: bytes)
300
- bytes << origin
301
- length += 2 + origin.bytesize
330
+ append_str(bytes, origin)
302
331
  end
303
-
304
332
  end
305
333
 
306
334
  # Process padding.
@@ -317,14 +345,21 @@ module HTTP2
317
345
  raise CompressionError, "Invalid padding #{padlen}"
318
346
  end
319
347
 
348
+ # make sure the buffer is binary and unfrozen
349
+ if bytes.frozen?
350
+ bytes = bytes.b
351
+ else
352
+ bytes.force_encoding(Encoding::BINARY)
353
+ end
354
+
320
355
  length += padlen
321
356
  pack([padlen -= 1], UINT8, buffer: bytes, offset: 0)
322
- frame[:flags] << :padded
357
+ frame[:flags] += [:padded]
323
358
 
324
359
  # Padding: Padding octets that contain no application semantic value.
325
360
  # Padding octets MUST be set to zero when sending and ignored when
326
361
  # receiving.
327
- bytes << ("\0" * padlen)
362
+ append_str(bytes, ("\0" * padlen))
328
363
  end
329
364
 
330
365
  frame[:length] = length
@@ -339,22 +374,27 @@ module HTTP2
339
374
  return if buf.size < 9
340
375
 
341
376
  frame = read_common_header(buf)
342
- return if buf.size < 9 + frame[:length]
343
377
 
344
- raise ProtocolError, "payload too large" if frame[:length] > @local_max_frame_size
378
+ type = frame[:type]
379
+ length = frame[:length]
380
+ flags = frame[:flags]
381
+
382
+ return if buf.size < 9 + length
383
+
384
+ raise ProtocolError, "payload too large" if length > @local_max_frame_size
345
385
 
346
386
  read_str(buf, 9)
347
- payload = read_str(buf, frame[:length])
387
+ payload = read_str(buf, length)
348
388
 
349
389
  # Implementations MUST discard frames
350
390
  # that have unknown or unsupported types.
351
391
  # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.5
352
- return frame if frame[:type].nil?
392
+ return frame unless type
353
393
 
354
394
  # Process padding
355
395
  padlen = 0
356
- if FRAME_TYPES_WITH_PADDING.include?(frame[:type])
357
- padded = frame[:flags].include?(:padded)
396
+ if FRAME_TYPES_WITH_PADDING.include?(type)
397
+ padded = flags.include?(:padded)
358
398
  if padded
359
399
  padlen = read_str(payload, 1).unpack1(UINT8)
360
400
  frame[:padding] = padlen + 1
@@ -362,15 +402,15 @@ module HTTP2
362
402
 
363
403
  payload = payload.byteslice(0, payload.bytesize - padlen) if padlen > 0
364
404
  frame[:length] -= frame[:padding]
365
- frame[:flags].delete(:padded)
405
+ flags.delete(:padded)
366
406
  end
367
407
  end
368
408
 
369
- case frame[:type]
409
+ case type
370
410
  when :data, :ping, :continuation
371
- frame[:payload] = read_str(payload, frame[:length])
411
+ frame[:payload] = read_str(payload, length)
372
412
  when :headers
373
- if frame[:flags].include? :priority
413
+ if flags.include?(:priority)
374
414
  e_sd = read_uint32(payload)
375
415
  frame[:dependency] = e_sd & RBIT
376
416
  frame[:exclusive] = e_sd.anybits?(EBIT)
@@ -378,9 +418,9 @@ module HTTP2
378
418
  frame[:weight] = weight
379
419
  payload = payload.byteslice(1..-1)
380
420
  end
381
- frame[:payload] = read_str(payload, frame[:length])
421
+ frame[:payload] = read_str(payload, length)
382
422
  when :priority
383
- raise FrameSizeError, "Invalid length for PRIORITY_STREAM (#{frame[:length]} != 5)" if frame[:length] != 5
423
+ raise FrameSizeError, "Invalid length for PRIORITY_STREAM (#{length} != 5)" if length != 5
384
424
 
385
425
  e_sd = read_uint32(payload)
386
426
  frame[:dependency] = e_sd & RBIT
@@ -389,7 +429,7 @@ module HTTP2
389
429
  frame[:weight] = weight
390
430
  payload = payload.byteslice(1..-1)
391
431
  when :rst_stream
392
- raise FrameSizeError, "Invalid length for RST_STREAM (#{frame[:length]} != 4)" if frame[:length] != 4
432
+ raise FrameSizeError, "Invalid length for RST_STREAM (#{length} != 4)" if length != 4
393
433
 
394
434
  frame[:error] = unpack_error read_uint32(payload)
395
435
 
@@ -397,9 +437,9 @@ module HTTP2
397
437
  # NOTE: frame[:length] might not match the number of frame[:payload]
398
438
  # because unknown extensions are ignored.
399
439
  frame[:payload] = []
400
- raise ProtocolError, "Invalid settings payload length" unless (frame[:length] % 6).zero?
440
+ raise ProtocolError, "Invalid settings payload length" unless (length % 6).zero?
401
441
 
402
- raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
442
+ raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if frame[:stream].nonzero?
403
443
 
404
444
  (frame[:length] / 6).times do
405
445
  id = read_str(payload, 2).unpack1(UINT16)
@@ -412,17 +452,15 @@ module HTTP2
412
452
  end
413
453
  when :push_promise
414
454
  frame[:promise_stream] = read_uint32(payload) & RBIT
415
- frame[:payload] = read_str(payload, frame[:length])
455
+ frame[:payload] = read_str(payload, length)
416
456
  when :goaway
417
457
  frame[:last_stream] = read_uint32(payload) & RBIT
418
458
  frame[:error] = unpack_error read_uint32(payload)
419
459
 
420
- size = frame[:length] - 8 # for last_stream and error
460
+ size = length - 8 # for last_stream and error
421
461
  frame[:payload] = read_str(payload, size) if size > 0
422
462
  when :window_update
423
- if frame[:length] % 4 != 0
424
- raise FrameSizeError, "Invalid length for WINDOW_UPDATE (#{frame[:length]} not multiple of 4)"
425
- end
463
+ raise FrameSizeError, "Invalid length for WINDOW_UPDATE (#{length} not multiple of 4)" if length % 4 != 0
426
464
 
427
465
  frame[:increment] = read_uint32(payload) & RBIT
428
466
  when :altsvc