http-2 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,36 @@ module HTTP2
3
3
  # Default connection and stream flow control window (64KB).
4
4
  DEFAULT_FLOW_WINDOW = 65535
5
5
 
6
+ # Default header table size
7
+ DEFAULT_HEADER_SIZE = 4096
8
+
9
+ # Default stream_limit
10
+ DEFAULT_MAX_CONCURRENT_STREAMS = 100
11
+
12
+ # Default values for SETTINGS frame, as defined by the spec.
13
+ SPEC_DEFAULT_CONNECTION_SETTINGS = {
14
+ settings_header_table_size: 4096,
15
+ settings_enable_push: 1, # enabled for servers
16
+ settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
17
+ settings_initial_window_size: 65535,
18
+ settings_max_frame_size: 16384,
19
+ settings_max_header_list_size: 2**31 - 1, # unlimited
20
+ }.freeze
21
+
22
+ DEFAULT_CONNECTION_SETTINGS = {
23
+ settings_header_table_size: 4096,
24
+ settings_enable_push: 1, # enabled for servers
25
+ settings_max_concurrent_streams: 100,
26
+ settings_initial_window_size: 65535, #
27
+ settings_max_frame_size: 16384,
28
+ settings_max_header_list_size: 2**31 - 1, # unlimited
29
+ }.freeze
30
+
6
31
  # Default stream priority (lower values are higher priority).
7
- DEFAULT_PRIORITY = 2**30
32
+ DEFAULT_WEIGHT = 16
8
33
 
9
34
  # Default connection "fast-fail" preamble string as defined by the spec.
10
- CONNECTION_HEADER = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
35
+ CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
11
36
 
12
37
  # Connection encapsulates all of the connection, stream, flow-control,
13
38
  # error management, and other processing logic required for a well-behaved
@@ -28,11 +53,17 @@ module HTTP2
28
53
 
29
54
  # Size of current connection flow control window (by default, set to
30
55
  # infinity, but is automatically updated on receipt of peer settings).
31
- attr_reader :window
56
+ attr_reader :local_window
57
+ attr_reader :remote_window
58
+ alias :window :local_window
59
+
60
+ # Current settings value for local and peer
61
+ attr_reader :local_settings
62
+ attr_reader :remote_settings
32
63
 
33
- # Maximum number of concurrent streams allowed by the peer (automatically
34
- # updated on receipt of peer settings).
35
- attr_reader :stream_limit
64
+ # Pending settings value
65
+ # Sent but not ack'ed settings
66
+ attr_reader :pending_settings
36
67
 
37
68
  # Number of active streams between client and server (reserved streams
38
69
  # are not counted towards the stream limit).
@@ -40,14 +71,23 @@ module HTTP2
40
71
 
41
72
  # Initializes new connection object.
42
73
  #
43
- def initialize(streams: 100, window: DEFAULT_FLOW_WINDOW)
44
- @stream_limit = streams
74
+ def initialize(**settings)
75
+ @local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
76
+ @remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
77
+
78
+ @compressor = Header::Compressor.new(settings)
79
+ @decompressor = Header::Decompressor.new(settings)
80
+
45
81
  @active_stream_count = 0
46
82
  @streams = {}
83
+ @pending_settings = []
47
84
 
48
85
  @framer = Framer.new
49
- @window = window
50
- @window_limit = window
86
+
87
+ @local_window_limit = @local_settings[:settings_initial_window_size]
88
+ @local_window = @local_window_limit
89
+ @remote_window_limit = @remote_settings[:settings_initial_window_size]
90
+ @remote_window = @remote_window_limit
51
91
 
52
92
  @recv_buffer = Buffer.new
53
93
  @send_buffer = []
@@ -60,11 +100,11 @@ module HTTP2
60
100
  # @param priority [Integer]
61
101
  # @param window [Integer]
62
102
  # @param parent [Stream]
63
- def new_stream(priority: DEFAULT_PRIORITY, parent: nil)
103
+ def new_stream(**args)
64
104
  raise ConnectionClosed.new if @state == :closed
65
- raise StreamLimitExceeded.new if @active_stream_count == @stream_limit
105
+ raise StreamLimitExceeded.new if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
66
106
 
67
- stream = activate_stream(@stream_id, priority, parent)
107
+ stream = activate_stream(id: @stream_id, **args)
68
108
  @stream_id += 2
69
109
 
70
110
  stream
@@ -76,7 +116,7 @@ module HTTP2
76
116
  # @param blk [Proc] callback to execute when PONG is received
77
117
  def ping(payload, &blk)
78
118
  send({type: :ping, stream: 0, payload: payload})
79
- once(:pong, &blk) if blk
119
+ once(:ack, &blk) if blk
80
120
  end
81
121
 
82
122
  # Sends a GOAWAY frame indicating that the peer should stop creating
@@ -97,20 +137,17 @@ module HTTP2
97
137
  @state = :closed
98
138
  end
99
139
 
100
- # Sends a connection SETTINGS frame to the peer. Setting window size
101
- # to Float::INFINITY disables flow control.
140
+ # Sends a connection SETTINGS frame to the peer.
141
+ # The values are reflected when the corresponding ACK is received.
102
142
  #
103
- # @param stream_limit [Integer] maximum number of concurrent streams
104
- # @param window_limit [Float] maximum flow window size
105
- def settings(stream_limit: @stream_limit, window_limit: @window_limit)
106
- payload = { settings_max_concurrent_streams: stream_limit }
107
- if window_limit.to_f.infinite?
108
- payload[:settings_flow_control_options] = 1
109
- else
110
- payload[:settings_initial_window_size] = window_limit
111
- end
112
-
143
+ # @param settings [Array or Hash]
144
+ def settings(payload)
145
+ payload = payload.to_a
146
+ check = validate_settings(@local_role, payload)
147
+ check and connection_error
148
+ @pending_settings << payload
113
149
  send({type: :settings, stream: 0, payload: payload})
150
+ @pending_settings << payload
114
151
  end
115
152
 
116
153
  # Decodes incoming bytes into HTTP 2.0 frames and routes them to
@@ -128,23 +165,27 @@ module HTTP2
128
165
  #
129
166
  # Client connection header is 24 byte connection header followed by
130
167
  # SETTINGS frame. Server connection header is SETTINGS frame only.
131
- if @state == :new
168
+ if @state == :waiting_magic
132
169
  if @recv_buffer.size < 24
133
- if !CONNECTION_HEADER.start_with? @recv_buffer
170
+ if !CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
134
171
  raise HandshakeError.new
135
172
  else
136
- return
173
+ return # maybe next time
137
174
  end
138
175
 
139
- elsif @recv_buffer.read(24) != CONNECTION_HEADER
176
+ elsif @recv_buffer.read(24) != CONNECTION_PREFACE_MAGIC
140
177
  raise HandshakeError.new
141
178
  else
142
- @state = :connection_header
143
- settings(stream_limit: @stream_limit, window_limit: @window_limit)
179
+ # MAGIC is OK. Send our settings
180
+ @state = :waiting_connection_preface
181
+ payload = @local_settings.select {|k,v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k]}
182
+ settings(payload)
144
183
  end
145
184
  end
146
185
 
147
186
  while frame = @framer.parse(@recv_buffer) do
187
+ emit(:frame_received, frame)
188
+
148
189
  # Header blocks MUST be transmitted as a contiguous sequence of frames
149
190
  # with no interleaved frames of any other type, or from any other stream.
150
191
  if !@continuation.empty?
@@ -156,21 +197,14 @@ module HTTP2
156
197
  @continuation << frame
157
198
  return if !frame[:flags].include? :end_headers
158
199
 
159
- headers = @continuation.collect do |chunk|
160
- decode_headers(chunk)
161
- chunk[:payload]
162
- end.flatten(1)
200
+ payload = @continuation.map {|f| f[:payload]}.join
163
201
 
164
202
  frame = @continuation.shift
165
203
  @continuation.clear
166
204
 
167
205
  frame.delete(:length)
168
- frame[:payload] = headers
169
- frame[:flags] << if frame[:type] == :push_promise
170
- :end_push_promise
171
- else
172
- :end_headers
173
- end
206
+ frame[:payload] = Buffer.new(payload)
207
+ frame[:flags] << :end_headers
174
208
  end
175
209
 
176
210
  # SETTINGS frames always apply to a connection, never a single stream.
@@ -200,8 +234,10 @@ module HTTP2
200
234
 
201
235
  stream = @streams[frame[:stream]]
202
236
  if stream.nil?
203
- stream = activate_stream(frame[:stream],
204
- frame[:priority] || DEFAULT_PRIORITY)
237
+ stream = activate_stream(id: frame[:stream],
238
+ weight: frame[:weight] || DEFAULT_WEIGHT,
239
+ dependency: frame[:dependency] || 0,
240
+ exclusive: frame[:exclusive] || false)
205
241
  emit(:stream, stream)
206
242
  end
207
243
 
@@ -209,8 +245,8 @@ module HTTP2
209
245
 
210
246
  when :push_promise
211
247
  # The last frame in a sequence of PUSH_PROMISE/CONTINUATION
212
- # frames MUST have the END_PUSH_PROMISE/END_HEADERS flag set
213
- if !frame[:flags].include? :end_push_promise
248
+ # frames MUST have the END_HEADERS flag set
249
+ if !frame[:flags].include? :end_headers
214
250
  @continuation << frame
215
251
  return
216
252
  end
@@ -248,7 +284,7 @@ module HTTP2
248
284
  end
249
285
  end
250
286
 
251
- stream = activate_stream(pid, DEFAULT_PRIORITY, parent)
287
+ stream = activate_stream(id: pid, parent: parent)
252
288
  emit(:promise, stream)
253
289
  stream << frame
254
290
  else
@@ -263,7 +299,7 @@ module HTTP2
263
299
  end
264
300
  end
265
301
 
266
- rescue
302
+ rescue => e
267
303
  connection_error
268
304
  end
269
305
  alias :<< :receive
@@ -277,6 +313,7 @@ module HTTP2
277
313
  # @note all frames are currently delivered in FIFO order.
278
314
  # @param frame [Hash]
279
315
  def send(frame)
316
+ emit(:frame_sent, frame)
280
317
  if frame[:type] == :data
281
318
  send_data(frame, true)
282
319
 
@@ -288,7 +325,9 @@ module HTTP2
288
325
  goaway(frame[:error])
289
326
  end
290
327
  else
291
- emit(:frame, encode(frame))
328
+ # HEADERS and PUSH_PROMISE may generate CONTINUATION
329
+ frames = encode(frame)
330
+ frames.each {|f| emit(:frame, f) }
292
331
  end
293
332
  end
294
333
  end
@@ -296,14 +335,18 @@ module HTTP2
296
335
  # Applies HTTP 2.0 binary encoding to the frame.
297
336
  #
298
337
  # @param frame [Hash]
299
- # @return [Buffer] encoded frame
338
+ # @return [Array of Buffer] encoded frame
300
339
  def encode(frame)
340
+ frames = []
341
+
301
342
  if frame[:type] == :headers ||
302
343
  frame[:type] == :push_promise
303
- encode_headers(frame)
344
+ frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
345
+ else
346
+ frames = [frame] # otherwise one frame
304
347
  end
305
348
 
306
- @framer.generate(frame)
349
+ frames.map {|f| @framer.generate(f) }
307
350
  end
308
351
 
309
352
  # Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
@@ -327,26 +370,25 @@ module HTTP2
327
370
  # @param frame [Hash]
328
371
  def connection_management(frame)
329
372
  case @state
330
- when :connection_header
331
- # SETTINGS frames MUST be sent at the start of a connection.
332
- connection_settings(frame)
373
+ when :waiting_connection_preface
374
+ # The first frame MUST be a SETTINGS frame at the start of a connection.
333
375
  @state = :connected
376
+ connection_settings(frame)
334
377
 
335
378
  when :connected
336
379
  case frame[:type]
337
380
  when :settings
338
381
  connection_settings(frame)
339
382
  when :window_update
340
- flow_control_allowed?
341
- @window += frame[:increment]
383
+ @remote_window += frame[:increment]
342
384
  send_data(nil, true)
343
385
  when :ping
344
- if frame[:flags].include? :pong
345
- emit(:pong, frame[:payload])
386
+ if frame[:flags].include? :ack
387
+ emit(:ack, frame[:payload])
346
388
  else
347
389
  send({
348
390
  type: :ping, stream: 0,
349
- flags: [:pong], payload: frame[:payload]
391
+ flags: [:ack], payload: frame[:payload]
350
392
  })
351
393
  end
352
394
  when :goaway
@@ -355,7 +397,8 @@ module HTTP2
355
397
  # for new streams.
356
398
  @state = :closed
357
399
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
358
-
400
+ when :altsvc, :blocked
401
+ emit(frame[:type], frame)
359
402
  else
360
403
  connection_error
361
404
  end
@@ -364,7 +407,60 @@ module HTTP2
364
407
  end
365
408
  end
366
409
 
367
- # Update local connection settings based on parameters set by the peer.
410
+ # Validate settings parameters. See sepc Section 6.5.2.
411
+ #
412
+ # @param role [Symbol] The sender's role: :client or :server
413
+ # @return nil if no error. Exception object in case of any error.
414
+ def validate_settings(role, settings)
415
+ settings.each do |key,v|
416
+ case key
417
+ when :settings_header_table_size
418
+ # Any value is valid
419
+ when :settings_enable_push
420
+ case role
421
+ when :server
422
+ # Section 8.2
423
+ # Clients MUST reject any attempt to change the
424
+ # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
425
+ # 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
429
+ when :client
430
+ # Any value other than 0 or 1 MUST be treated as a
431
+ # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
432
+ unless v == 0 || v == 1
433
+ return ProtocolError.new("invalid #{key} value")
434
+ end
435
+ end
436
+ when :settings_max_concurrent_streams
437
+ # Any value is valid
438
+ when :settings_initial_window_size
439
+ # Values above the maximum flow control window size of 2^31-1 MUST
440
+ # be treated as a connection error (Section 5.4.1) of type
441
+ # FLOW_CONTROL_ERROR.
442
+ unless v <= 0x7fffffff
443
+ return FlowControlError.new("invalid #{key} value")
444
+ end
445
+ when :settings_max_frame_size
446
+ # The initial value is 2^14 (16,384) octets. The value advertised
447
+ # by an endpoint MUST be between this initial value and the maximum
448
+ # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
449
+ # Values outside this range MUST be treated as a connection error
450
+ # (Section 5.4.1) of type PROTOCOL_ERROR.
451
+ unless 16384 <= v && v <= 16777215
452
+ return ProtocolError.new("invalid #{key} value")
453
+ end
454
+ when :settings_max_header_list_size
455
+ # Any value is valid
456
+ else
457
+ # ignore unknown settings
458
+ end
459
+ end
460
+ nil
461
+ end
462
+
463
+ # Update connection settings based on parameters set by the peer.
368
464
  #
369
465
  # @param frame [Hash]
370
466
  def connection_settings(frame)
@@ -372,35 +468,85 @@ module HTTP2
372
468
  connection_error
373
469
  end
374
470
 
375
- frame[:payload].each do |key,v|
471
+ # Apply settings.
472
+ # side =
473
+ # local: previously sent and pended our settings should be effective
474
+ # 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
484
+
485
+ settings.each do |key,v|
486
+ case side
487
+ when :local
488
+ @local_settings[key] = v
489
+ when :remote
490
+ @remote_settings[key] = v
491
+ end
492
+
376
493
  case key
377
494
  when :settings_max_concurrent_streams
378
- @stream_limit = v
495
+ # Do nothing.
496
+ # The value controls at the next attempt of stream creation.
379
497
 
380
- # A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the available
381
- # space in a flow control window to become negative. A sender MUST
382
- # track the negative flow control window, and MUST NOT send new flow
383
- # controlled frames until it receives WINDOW_UPDATE frames that cause
384
- # the flow control window to become positive.
385
498
  when :settings_initial_window_size
386
- flow_control_allowed?
387
- @window = @window - @window_limit + v
388
- @streams.each do |id, stream|
389
- stream.emit(:window, stream.window - @window_limit + v)
390
- end
499
+ # A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the available
500
+ # space in a flow control window to become negative. A sender MUST
501
+ # track the negative flow control window, and MUST NOT send new flow
502
+ # controlled frames until it receives WINDOW_UPDATE frames that cause
503
+ # the flow control window to become positive.
504
+ case side
505
+ when :local
506
+ @local_window = @local_window - @local_window_limit + v
507
+ @streams.each do |id, stream|
508
+ stream.emit(:local_window, stream.local_window - @local_window_limit + v)
509
+ end
391
510
 
392
- @window_limit = v
511
+ @local_window_limit = v
512
+ when :remote
513
+ @remote_window = @remote_window - @remote_window_limit + v
514
+ @streams.each do |id, stream|
515
+ # Event name is :window, not :remote_window
516
+ stream.emit(:window, stream.remote_window - @remote_window_limit + v)
517
+ end
393
518
 
394
- # Flow control can be disabled the entire connection using the
395
- # SETTINGS_FLOW_CONTROL_OPTIONS setting. This setting ends all forms
396
- # of flow control. An implementation that does not wish to perform
397
- # flow control can use this in the initial SETTINGS exchange.
398
- when :settings_flow_control_options
399
- flow_control_allowed?
519
+ @remote_window_limit = v
520
+ end
400
521
 
401
- if v == 1
402
- @window = @window_limit = Float::INFINITY
522
+ when :settings_header_table_size
523
+ # Setting header table size might cause some headers evicted
524
+ case side
525
+ when :local
526
+ @decompressor.set_table_size(v)
527
+ when :remote
528
+ @compressor.set_table_size(v)
403
529
  end
530
+
531
+ when :settings_enable_push
532
+ # nothing to do
533
+
534
+ when :settings_max_frame_size
535
+ # nothing to do
536
+
537
+ else
538
+ # ignore unknown settings
539
+ end
540
+ end
541
+
542
+ case side
543
+ when :local
544
+ # Received a settings_ack. Notify application layer.
545
+ emit(:settings_ack, frame, @pending_settings.size)
546
+ when :remote
547
+ if @state != :closed
548
+ # Send ack to peer
549
+ send({type: :settings, stream: 0, payload: [], flags: [:ack]})
404
550
  end
405
551
  end
406
552
  end
@@ -425,21 +571,34 @@ module HTTP2
425
571
  # Encode headers payload and update connection compressor state.
426
572
  #
427
573
  # @param frame [Hash]
574
+ # @return [Array of Frame]
428
575
  def encode_headers(frame)
429
- if !frame[:payload].is_a? String
430
- frame[:payload] = @compressor.encode(frame[:payload])
576
+ payload = frame[:payload]
577
+ unless payload.is_a? String
578
+ payload = @compressor.encode(payload)
431
579
  end
432
580
 
433
- rescue Exception => e
434
- connection_error(:compression_error, msg: e.message)
435
- end
581
+ frames = []
436
582
 
437
- # Once disabled, no further flow control operations are permitted.
438
- #
439
- def flow_control_allowed?
440
- if @window_limit == Float::INFINITY
441
- connection_error(:flow_control_error)
583
+ while payload.size > 0
584
+ cont = frame.dup
585
+ cont[:type] = :continuation
586
+ cont[:flags] = []
587
+ cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
588
+ frames << cont
589
+ end
590
+ if frames.empty?
591
+ frames = [frame]
592
+ else
593
+ frames.first[:type] = frame[:type]
594
+ frames.first[:flags] = frame[:flags] - [:end_headers]
595
+ frames.last[:flags] << :end_headers
442
596
  end
597
+
598
+ frames
599
+
600
+ rescue Exception => e
601
+ [connection_error(:compression_error, msg: e.message)]
443
602
  end
444
603
 
445
604
  # Activates new incoming or outgoing stream and registers appropriate
@@ -449,12 +608,12 @@ module HTTP2
449
608
  # @param priority [Integer]
450
609
  # @param window [Integer]
451
610
  # @param parent [Stream]
452
- def activate_stream(id, priority, parent = nil)
611
+ def activate_stream(id: nil, **args)
453
612
  if @streams.key?(id)
454
613
  connection_error(msg: 'Stream ID already exists')
455
614
  end
456
615
 
457
- stream = Stream.new(id, priority, @window_limit, parent)
616
+ stream = Stream.new({connection: self, id: id}.merge(args))
458
617
 
459
618
  # Streams that are in the "open" state, or either of the "half closed"
460
619
  # states count toward the maximum number of streams that an endpoint is