http-2 0.7.0 → 0.8.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.
@@ -1,7 +1,6 @@
1
1
  module HTTP2
2
-
3
2
  # Default connection and stream flow control window (64KB).
4
- DEFAULT_FLOW_WINDOW = 65535
3
+ DEFAULT_FLOW_WINDOW = 65_535
5
4
 
6
5
  # Default header table size
7
6
  DEFAULT_HEADER_SIZE = 4096
@@ -14,8 +13,8 @@ module HTTP2
14
13
  settings_header_table_size: 4096,
15
14
  settings_enable_push: 1, # enabled for servers
16
15
  settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
17
- settings_initial_window_size: 65535,
18
- settings_max_frame_size: 16384,
16
+ settings_initial_window_size: 65_535,
17
+ settings_max_frame_size: 16_384,
19
18
  settings_max_header_list_size: 2**31 - 1, # unlimited
20
19
  }.freeze
21
20
 
@@ -23,8 +22,8 @@ module HTTP2
23
22
  settings_header_table_size: 4096,
24
23
  settings_enable_push: 1, # enabled for servers
25
24
  settings_max_concurrent_streams: 100,
26
- settings_initial_window_size: 65535, #
27
- settings_max_frame_size: 16384,
25
+ settings_initial_window_size: 65_535, #
26
+ settings_max_frame_size: 16_384,
28
27
  settings_max_header_list_size: 2**31 - 1, # unlimited
29
28
  }.freeze
30
29
 
@@ -32,7 +31,7 @@ module HTTP2
32
31
  DEFAULT_WEIGHT = 16
33
32
 
34
33
  # Default connection "fast-fail" preamble string as defined by the spec.
35
- CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
34
+ CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
36
35
 
37
36
  # Connection encapsulates all of the connection, stream, flow-control,
38
37
  # error management, and other processing logic required for a well-behaved
@@ -40,6 +39,8 @@ module HTTP2
40
39
  #
41
40
  # Note that this class should not be used directly. Instead, you want to
42
41
  # use either Client or Server class to drive the HTTP 2.0 exchange.
42
+ #
43
+ # rubocop:disable ClassLength
43
44
  class Connection
44
45
  include FlowBuffer
45
46
  include Emitter
@@ -55,7 +56,7 @@ module HTTP2
55
56
  # infinity, but is automatically updated on receipt of peer settings).
56
57
  attr_reader :local_window
57
58
  attr_reader :remote_window
58
- alias :window :local_window
59
+ alias_method :window, :local_window
59
60
 
60
61
  # Current settings value for local and peer
61
62
  attr_reader :local_settings
@@ -101,8 +102,8 @@ module HTTP2
101
102
  # @param window [Integer]
102
103
  # @param parent [Stream]
103
104
  def new_stream(**args)
104
- raise ConnectionClosed.new if @state == :closed
105
- raise StreamLimitExceeded.new if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
105
+ fail ConnectionClosed if @state == :closed
106
+ fail StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
106
107
 
107
108
  stream = activate_stream(id: @stream_id, **args)
108
109
  @stream_id += 2
@@ -115,7 +116,7 @@ module HTTP2
115
116
  # @param payload [String] optional payload must be 8 bytes long
116
117
  # @param blk [Proc] callback to execute when PONG is received
117
118
  def ping(payload, &blk)
118
- send({type: :ping, stream: 0, payload: payload})
119
+ send(type: :ping, stream: 0, payload: payload)
119
120
  once(:ack, &blk) if blk
120
121
  end
121
122
 
@@ -130,10 +131,14 @@ module HTTP2
130
131
  # @param error [Symbol]
131
132
  # @param payload [String]
132
133
  def goaway(error = :no_error, payload = nil)
133
- send({
134
- type: :goaway, last_stream: (@streams.max.first rescue 0),
135
- error: error, payload: payload
136
- })
134
+ last_stream = if (max = @streams.max)
135
+ max.first
136
+ else
137
+ 0
138
+ end
139
+
140
+ send(type: :goaway, last_stream: last_stream,
141
+ error: error, payload: payload)
137
142
  @state = :closed
138
143
  end
139
144
 
@@ -143,10 +148,9 @@ module HTTP2
143
148
  # @param settings [Array or Hash]
144
149
  def settings(payload)
145
150
  payload = payload.to_a
146
- check = validate_settings(@local_role, payload)
147
- check and connection_error
151
+ connection_error if validate_settings(@local_role, payload)
148
152
  @pending_settings << payload
149
- send({type: :settings, stream: 0, payload: payload})
153
+ send(type: :settings, stream: 0, payload: payload)
150
154
  @pending_settings << payload
151
155
  end
152
156
 
@@ -168,36 +172,34 @@ module HTTP2
168
172
  if @state == :waiting_magic
169
173
  if @recv_buffer.size < 24
170
174
  if !CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
171
- raise HandshakeError.new
175
+ fail HandshakeError
172
176
  else
173
177
  return # maybe next time
174
178
  end
175
-
176
- elsif @recv_buffer.read(24) != CONNECTION_PREFACE_MAGIC
177
- raise HandshakeError.new
178
- else
179
+ elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
179
180
  # MAGIC is OK. Send our settings
180
181
  @state = :waiting_connection_preface
181
- payload = @local_settings.select {|k,v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k]}
182
+ payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
182
183
  settings(payload)
184
+ else
185
+ fail HandshakeError
183
186
  end
184
187
  end
185
188
 
186
- while frame = @framer.parse(@recv_buffer) do
189
+ while (frame = @framer.parse(@recv_buffer))
187
190
  emit(:frame_received, frame)
188
191
 
189
192
  # Header blocks MUST be transmitted as a contiguous sequence of frames
190
193
  # with no interleaved frames of any other type, or from any other stream.
191
- if !@continuation.empty?
192
- if frame[:type] != :continuation ||
193
- frame[:stream] != @continuation.first[:stream]
194
+ unless @continuation.empty?
195
+ unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
194
196
  connection_error
195
197
  end
196
198
 
197
199
  @continuation << frame
198
- return if !frame[:flags].include? :end_headers
200
+ return unless frame[:flags].include? :end_headers
199
201
 
200
- payload = @continuation.map {|f| f[:payload]}.join
202
+ payload = @continuation.map { |f| f[:payload] }.join
201
203
 
202
204
  frame = @continuation.shift
203
205
  @continuation.clear
@@ -219,7 +221,7 @@ module HTTP2
219
221
  when :headers
220
222
  # The last frame in a sequence of HEADERS/CONTINUATION
221
223
  # frames MUST have the END_HEADERS flag set.
222
- if !frame[:flags].include? :end_headers
224
+ unless frame[:flags].include? :end_headers
223
225
  @continuation << frame
224
226
  return
225
227
  end
@@ -234,10 +236,12 @@ module HTTP2
234
236
 
235
237
  stream = @streams[frame[:stream]]
236
238
  if stream.nil?
237
- stream = activate_stream(id: frame[:stream],
238
- weight: frame[:weight] || DEFAULT_WEIGHT,
239
- dependency: frame[:dependency] || 0,
240
- exclusive: frame[:exclusive] || false)
239
+ stream = activate_stream(
240
+ id: frame[:stream],
241
+ weight: frame[:weight] || DEFAULT_WEIGHT,
242
+ dependency: frame[:dependency] || 0,
243
+ exclusive: frame[:exclusive] || false,
244
+ )
241
245
  emit(:stream, stream)
242
246
  end
243
247
 
@@ -246,7 +250,7 @@ module HTTP2
246
250
  when :push_promise
247
251
  # The last frame in a sequence of PUSH_PROMISE/CONTINUATION
248
252
  # frames MUST have the END_HEADERS flag set
249
- if !frame[:flags].include? :end_headers
253
+ unless frame[:flags].include? :end_headers
250
254
  @continuation << frame
251
255
  return
252
256
  end
@@ -269,7 +273,7 @@ module HTTP2
269
273
 
270
274
  connection_error(msg: 'missing parent ID') if parent.nil?
271
275
 
272
- if !(parent.state == :open || parent.state == :half_closed_local)
276
+ unless parent.state == :open || parent.state == :half_closed_local
273
277
  # An endpoint might receive a PUSH_PROMISE frame after it sends
274
278
  # RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
275
279
  # The RST_STREAM does not cancel any promised stream. Therefore, if
@@ -278,7 +282,7 @@ module HTTP2
278
282
  if parent.closed == :local_rst
279
283
  # We can either (a) 'resurrect' the parent, or (b) RST_STREAM
280
284
  # ... sticking with (b), might need to revisit later.
281
- send({type: :rst_stream, stream: pid, error: :refused_stream})
285
+ send(type: :rst_stream, stream: pid, error: :refused_stream)
282
286
  else
283
287
  connection_error
284
288
  end
@@ -288,21 +292,38 @@ module HTTP2
288
292
  emit(:promise, stream)
289
293
  stream << frame
290
294
  else
291
- if stream = @streams[frame[:stream]]
295
+ if (stream = @streams[frame[:stream]])
292
296
  stream << frame
293
297
  else
294
- # An endpoint that receives an unexpected stream identifier
295
- # MUST respond with a connection error of type PROTOCOL_ERROR.
296
- connection_error
298
+ # The PRIORITY frame can be sent for a stream in the "idle" or
299
+ # "closed" state. This allows for the reprioritization of a
300
+ # group of dependent streams by altering the priority of an
301
+ # unused or closed parent stream.
302
+ if frame[:type] == :priority
303
+ stream = activate_stream(
304
+ id: frame[:stream],
305
+ weight: frame[:weight] || DEFAULT_WEIGHT,
306
+ dependency: frame[:dependency] || 0,
307
+ exclusive: frame[:exclusive] || false,
308
+ )
309
+
310
+ emit(:stream, stream)
311
+ stream << frame
312
+ else
313
+ # An endpoint that receives an unexpected stream identifier
314
+ # MUST respond with a connection error of type PROTOCOL_ERROR.
315
+ connection_error
316
+ end
297
317
  end
298
318
  end
299
319
  end
300
320
  end
301
321
 
302
322
  rescue => e
323
+ raise if e.is_a?(Error::Error)
303
324
  connection_error
304
325
  end
305
- alias :<< :receive
326
+ alias_method :<<, :receive
306
327
 
307
328
  private
308
329
 
@@ -320,14 +341,13 @@ module HTTP2
320
341
  else
321
342
  # An endpoint can end a connection at any time. In particular, an
322
343
  # endpoint MAY choose to treat a stream error as a connection error.
323
- if frame[:type] == :rst_stream
324
- if frame[:error] == :protocol_error
325
- goaway(frame[:error])
326
- end
344
+ if frame[:type] == :rst_stream && frame[:error] == :protocol_error
345
+ goaway(frame[:error])
327
346
  else
328
- # HEADERS and PUSH_PROMISE may generate CONTINUATION
347
+ # HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
348
+ # RST_STREAM that are not protocol errors
329
349
  frames = encode(frame)
330
- frames.each {|f| emit(:frame, f) }
350
+ frames.each { |f| emit(:frame, f) }
331
351
  end
332
352
  end
333
353
  end
@@ -339,14 +359,13 @@ module HTTP2
339
359
  def encode(frame)
340
360
  frames = []
341
361
 
342
- if frame[:type] == :headers ||
343
- frame[:type] == :push_promise
362
+ if frame[:type] == :headers || frame[:type] == :push_promise
344
363
  frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
345
364
  else
346
365
  frames = [frame] # otherwise one frame
347
366
  end
348
367
 
349
- frames.map {|f| @framer.generate(f) }
368
+ frames.map { |f| @framer.generate(f) }
350
369
  end
351
370
 
352
371
  # Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
@@ -356,9 +375,9 @@ module HTTP2
356
375
  # @return [Boolean]
357
376
  def connection_frame?(frame)
358
377
  frame[:stream] == 0 ||
359
- frame[:type] == :settings ||
360
- frame[:type] == :ping ||
361
- frame[:type] == :goaway
378
+ frame[:type] == :settings ||
379
+ frame[:type] == :ping ||
380
+ frame[:type] == :goaway
362
381
  end
363
382
 
364
383
  # Process received connection frame (stream ID = 0).
@@ -386,10 +405,8 @@ module HTTP2
386
405
  if frame[:flags].include? :ack
387
406
  emit(:ack, frame[:payload])
388
407
  else
389
- send({
390
- type: :ping, stream: 0,
391
- flags: [:ack], payload: frame[:payload]
392
- })
408
+ send(type: :ping, stream: 0,
409
+ flags: [:ack], payload: frame[:payload])
393
410
  end
394
411
  when :goaway
395
412
  # Receivers of a GOAWAY frame MUST NOT open additional streams on
@@ -412,7 +429,7 @@ module HTTP2
412
429
  # @param role [Symbol] The sender's role: :client or :server
413
430
  # @return nil if no error. Exception object in case of any error.
414
431
  def validate_settings(role, settings)
415
- settings.each do |key,v|
432
+ settings.each do |key, v|
416
433
  case key
417
434
  when :settings_header_table_size
418
435
  # Any value is valid
@@ -423,9 +440,7 @@ module HTTP2
423
440
  # Clients MUST reject any attempt to change the
424
441
  # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
425
442
  # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
426
- unless v == 0
427
- return ProtocolError.new("invalid #{key} value")
428
- end
443
+ return ProtocolError.new("invalid #{key} value") unless v == 0
429
444
  when :client
430
445
  # Any value other than 0 or 1 MUST be treated as a
431
446
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
@@ -448,13 +463,12 @@ module HTTP2
448
463
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
449
464
  # Values outside this range MUST be treated as a connection error
450
465
  # (Section 5.4.1) of type PROTOCOL_ERROR.
451
- unless 16384 <= v && v <= 16777215
466
+ unless 16_384 <= v && v <= 16_777_215
452
467
  return ProtocolError.new("invalid #{key} value")
453
468
  end
454
469
  when :settings_max_header_list_size
455
470
  # Any value is valid
456
- else
457
- # ignore unknown settings
471
+ # else # ignore unknown settings
458
472
  end
459
473
  end
460
474
  nil
@@ -464,25 +478,21 @@ module HTTP2
464
478
  #
465
479
  # @param frame [Hash]
466
480
  def connection_settings(frame)
467
- if (frame[:type] != :settings || frame[:stream] != 0)
468
- connection_error
469
- end
481
+ connection_error unless frame[:type] == :settings && frame[:stream] == 0
470
482
 
471
483
  # Apply settings.
472
484
  # side =
473
485
  # local: previously sent and pended our settings should be effective
474
486
  # remote: just received peer settings should immediately be effective
475
- settings, side = \
476
- if frame[:flags].include?(:ack)
477
- # Process pending settings we have sent.
478
- [@pending_settings.shift, :local]
479
- else
480
- check = validate_settings(@remote_role, frame[:payload])
481
- check and connection_error(check)
482
- [frame[:payload], :remote]
483
- end
487
+ settings, side = if frame[:flags].include?(:ack)
488
+ # Process pending settings we have sent.
489
+ [@pending_settings.shift, :local]
490
+ else
491
+ connection_error(check) if validate_settings(@remote_role, frame[:payload])
492
+ [frame[:payload], :remote]
493
+ end
484
494
 
485
- settings.each do |key,v|
495
+ settings.each do |key, v|
486
496
  case side
487
497
  when :local
488
498
  @local_settings[key] = v
@@ -504,14 +514,14 @@ module HTTP2
504
514
  case side
505
515
  when :local
506
516
  @local_window = @local_window - @local_window_limit + v
507
- @streams.each do |id, stream|
517
+ @streams.each do |_id, stream|
508
518
  stream.emit(:local_window, stream.local_window - @local_window_limit + v)
509
519
  end
510
520
 
511
521
  @local_window_limit = v
512
522
  when :remote
513
523
  @remote_window = @remote_window - @remote_window_limit + v
514
- @streams.each do |id, stream|
524
+ @streams.each do |_id, stream|
515
525
  # Event name is :window, not :remote_window
516
526
  stream.emit(:window, stream.remote_window - @remote_window_limit + v)
517
527
  end
@@ -523,9 +533,9 @@ module HTTP2
523
533
  # Setting header table size might cause some headers evicted
524
534
  case side
525
535
  when :local
526
- @decompressor.set_table_size(v)
536
+ @decompressor.table_size = v
527
537
  when :remote
528
- @compressor.set_table_size(v)
538
+ @compressor.table_size = v
529
539
  end
530
540
 
531
541
  when :settings_enable_push
@@ -534,8 +544,7 @@ module HTTP2
534
544
  when :settings_max_frame_size
535
545
  # nothing to do
536
546
 
537
- else
538
- # ignore unknown settings
547
+ # else # ignore unknown settings
539
548
  end
540
549
  end
541
550
 
@@ -544,9 +553,9 @@ module HTTP2
544
553
  # Received a settings_ack. Notify application layer.
545
554
  emit(:settings_ack, frame, @pending_settings.size)
546
555
  when :remote
547
- if @state != :closed
556
+ unless @state == :closed || @h2c_upgrade == :start
548
557
  # Send ack to peer
549
- send({type: :settings, stream: 0, payload: [], flags: [:ack]})
558
+ send(type: :settings, stream: 0, payload: [], flags: [:ack])
550
559
  end
551
560
  end
552
561
  end
@@ -564,7 +573,7 @@ module HTTP2
564
573
  frame[:payload] = @decompressor.decode(frame[:payload])
565
574
  end
566
575
 
567
- rescue Exception => e
576
+ rescue => e
568
577
  connection_error(:compression_error, msg: e.message)
569
578
  end
570
579
 
@@ -574,9 +583,7 @@ module HTTP2
574
583
  # @return [Array of Frame]
575
584
  def encode_headers(frame)
576
585
  payload = frame[:payload]
577
- unless payload.is_a? String
578
- payload = @compressor.encode(payload)
579
- end
586
+ payload = @compressor.encode(payload) unless payload.is_a? String
580
587
 
581
588
  frames = []
582
589
 
@@ -592,12 +599,12 @@ module HTTP2
592
599
  else
593
600
  frames.first[:type] = frame[:type]
594
601
  frames.first[:flags] = frame[:flags] - [:end_headers]
595
- frames.last[:flags] << :end_headers
602
+ frames.last[:flags] << :end_headers
596
603
  end
597
604
 
598
605
  frames
599
606
 
600
- rescue Exception => e
607
+ rescue => e
601
608
  [connection_error(:compression_error, msg: e.message)]
602
609
  end
603
610
 
@@ -609,11 +616,9 @@ module HTTP2
609
616
  # @param window [Integer]
610
617
  # @param parent [Stream]
611
618
  def activate_stream(id: nil, **args)
612
- if @streams.key?(id)
613
- connection_error(msg: 'Stream ID already exists')
614
- end
619
+ connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
615
620
 
616
- stream = Stream.new({connection: self, id: id}.merge(args))
621
+ stream = Stream.new({ connection: self, id: id }.merge(args))
617
622
 
618
623
  # Streams that are in the "open" state, or either of the "half closed"
619
624
  # states count toward the maximum number of streams that an endpoint is
@@ -638,12 +643,11 @@ module HTTP2
638
643
  # @option error [Symbol] :compression_error
639
644
  # @param msg [String]
640
645
  def connection_error(error = :protocol_error, msg: nil)
641
- goaway(error) if @state != :closed && @state != :new
646
+ goaway(error) unless @state == :closed || @state == :new
642
647
 
643
648
  @state, @error = :closed, error
644
649
  klass = error.to_s.split('_').map(&:capitalize).join
645
- raise Error.const_get(klass).new(msg)
650
+ fail Error.const_get(klass), msg
646
651
  end
647
-
648
652
  end
649
653
  end