http-2 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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