http-2 0.11.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -9
- data/lib/http/2/base64.rb +45 -0
- data/lib/http/2/client.rb +19 -6
- data/lib/http/2/connection.rb +235 -163
- data/lib/http/2/emitter.rb +7 -5
- data/lib/http/2/error.rb +24 -1
- data/lib/http/2/extensions.rb +53 -0
- data/lib/http/2/flow_buffer.rb +91 -33
- data/lib/http/2/framer.rb +184 -157
- data/lib/http/2/header/compressor.rb +157 -0
- data/lib/http/2/header/decompressor.rb +144 -0
- data/lib/http/2/header/encoding_context.rb +337 -0
- data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
- data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
- data/lib/http/2/header.rb +35 -0
- data/lib/http/2/server.rb +47 -20
- data/lib/http/2/stream.rb +130 -61
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +14 -13
- data/sig/client.rbs +9 -0
- data/sig/connection.rbs +93 -0
- data/sig/emitter.rbs +13 -0
- data/sig/error.rbs +35 -0
- data/sig/extensions.rbs +5 -0
- data/sig/flow_buffer.rbs +21 -0
- data/sig/frame_buffer.rbs +13 -0
- data/sig/framer.rbs +54 -0
- data/sig/header/compressor.rbs +27 -0
- data/sig/header/decompressor.rbs +22 -0
- data/sig/header/encoding_context.rbs +34 -0
- data/sig/header/huffman.rbs +9 -0
- data/sig/header.rbs +27 -0
- data/sig/next.rbs +101 -0
- data/sig/server.rbs +12 -0
- data/sig/stream.rbs +91 -0
- metadata +38 -79
- data/.autotest +0 -20
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -131
- data/.travis.yml +0 -17
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- data/LICENSE +0 -21
- data/Rakefile +0 -49
- data/example/Gemfile +0 -3
- data/example/README.md +0 -44
- data/example/client.rb +0 -122
- data/example/helper.rb +0 -19
- data/example/keys/server.crt +0 -20
- data/example/keys/server.key +0 -27
- data/example/server.rb +0 -139
- data/example/upgrade_client.rb +0 -153
- data/example/upgrade_server.rb +0 -203
- data/http-2.gemspec +0 -22
- data/lib/http/2/buffer.rb +0 -76
- data/lib/http/2/compressor.rb +0 -572
- data/lib/tasks/generate_huffman_table.rb +0 -166
- data/spec/buffer_spec.rb +0 -28
- data/spec/client_spec.rb +0 -188
- data/spec/compressor_spec.rb +0 -666
- data/spec/connection_spec.rb +0 -681
- data/spec/emitter_spec.rb +0 -54
- data/spec/framer_spec.rb +0 -487
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +0 -317
- data/spec/helper.rb +0 -147
- data/spec/hpack_test_spec.rb +0 -84
- data/spec/huffman_spec.rb +0 -68
- data/spec/server_spec.rb +0 -52
- data/spec/stream_spec.rb +0 -878
- data/spec/support/deep_dup.rb +0 -55
- data/spec/support/duplicable.rb +0 -98
data/lib/http/2/connection.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTP2
|
2
4
|
# Default connection and stream flow control window (64KB).
|
3
5
|
DEFAULT_FLOW_WINDOW = 65_535
|
@@ -10,31 +12,33 @@ module HTTP2
|
|
10
12
|
|
11
13
|
# Default values for SETTINGS frame, as defined by the spec.
|
12
14
|
SPEC_DEFAULT_CONNECTION_SETTINGS = {
|
13
|
-
settings_header_table_size:
|
14
|
-
settings_enable_push:
|
15
|
-
settings_max_concurrent_streams:
|
16
|
-
settings_initial_window_size:
|
17
|
-
settings_max_frame_size:
|
18
|
-
settings_max_header_list_size:
|
15
|
+
settings_header_table_size: 4096,
|
16
|
+
settings_enable_push: 1, # enabled for servers
|
17
|
+
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
|
18
|
+
settings_initial_window_size: 65_535,
|
19
|
+
settings_max_frame_size: 16_384,
|
20
|
+
settings_max_header_list_size: (2 << 30) - 1 # unlimited
|
19
21
|
}.freeze
|
20
22
|
|
21
23
|
DEFAULT_CONNECTION_SETTINGS = {
|
22
|
-
settings_header_table_size:
|
23
|
-
settings_enable_push:
|
24
|
-
settings_max_concurrent_streams:
|
25
|
-
settings_initial_window_size:
|
26
|
-
settings_max_frame_size:
|
27
|
-
settings_max_header_list_size:
|
24
|
+
settings_header_table_size: 4096,
|
25
|
+
settings_enable_push: 1, # enabled for servers
|
26
|
+
settings_max_concurrent_streams: 100,
|
27
|
+
settings_initial_window_size: 65_535,
|
28
|
+
settings_max_frame_size: 16_384,
|
29
|
+
settings_max_header_list_size: (2 << 30) - 1 # unlimited
|
28
30
|
}.freeze
|
29
31
|
|
30
32
|
# Default stream priority (lower values are higher priority).
|
31
|
-
DEFAULT_WEIGHT
|
33
|
+
DEFAULT_WEIGHT = 16
|
32
34
|
|
33
35
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
34
|
-
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
36
|
+
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
37
|
+
|
38
|
+
REQUEST_MANDATORY_HEADERS = %w[:scheme :method :authority :path].freeze
|
39
|
+
RESPONSE_MANDATORY_HEADERS = %w[:status].freeze
|
35
40
|
|
36
|
-
|
37
|
-
RECENTLY_CLOSED_STREAMS_TTL = 15
|
41
|
+
EMPTY = [].freeze
|
38
42
|
|
39
43
|
# Connection encapsulates all of the connection, stream, flow-control,
|
40
44
|
# error management, and other processing logic required for a well-behaved
|
@@ -43,24 +47,25 @@ module HTTP2
|
|
43
47
|
# Note that this class should not be used directly. Instead, you want to
|
44
48
|
# use either Client or Server class to drive the HTTP 2.0 exchange.
|
45
49
|
#
|
46
|
-
# rubocop:disable ClassLength
|
50
|
+
# rubocop:disable Metrics/ClassLength
|
47
51
|
class Connection
|
48
52
|
include FlowBuffer
|
49
53
|
include Emitter
|
50
54
|
include Error
|
51
55
|
|
56
|
+
using StringExtensions
|
57
|
+
|
52
58
|
# Connection state (:new, :closed).
|
53
59
|
attr_reader :state
|
54
60
|
|
55
61
|
# Size of current connection flow control window (by default, set to
|
56
62
|
# infinity, but is automatically updated on receipt of peer settings).
|
57
63
|
attr_reader :local_window
|
58
|
-
attr_reader :remote_window
|
64
|
+
attr_reader :remote_window, :remote_settings
|
59
65
|
alias window local_window
|
60
66
|
|
61
67
|
# Current settings value for local and peer
|
62
68
|
attr_reader :local_settings
|
63
|
-
attr_reader :remote_settings
|
64
69
|
|
65
70
|
# Pending settings value
|
66
71
|
# Sent but not ack'ed settings
|
@@ -68,36 +73,38 @@ module HTTP2
|
|
68
73
|
|
69
74
|
# Number of active streams between client and server (reserved streams
|
70
75
|
# are not counted towards the stream limit).
|
71
|
-
|
76
|
+
attr_accessor :active_stream_count
|
72
77
|
|
73
78
|
# Initializes new connection object.
|
74
79
|
#
|
75
|
-
def initialize(
|
80
|
+
def initialize(settings = {})
|
76
81
|
@local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
|
77
82
|
@remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
|
78
83
|
|
79
|
-
@compressor = Header::Compressor.new(
|
80
|
-
@decompressor = Header::Decompressor.new(
|
84
|
+
@compressor = Header::Compressor.new(settings)
|
85
|
+
@decompressor = Header::Decompressor.new(settings)
|
81
86
|
|
82
87
|
@active_stream_count = 0
|
88
|
+
@last_activated_stream = 0
|
89
|
+
@last_stream_id = 0
|
83
90
|
@streams = {}
|
84
91
|
@streams_recently_closed = {}
|
85
92
|
@pending_settings = []
|
86
93
|
|
87
|
-
@framer = Framer.new
|
94
|
+
@framer = Framer.new(@local_settings[:settings_max_frame_size])
|
88
95
|
|
89
96
|
@local_window_limit = @local_settings[:settings_initial_window_size]
|
90
97
|
@local_window = @local_window_limit
|
91
98
|
@remote_window_limit = @remote_settings[:settings_initial_window_size]
|
92
99
|
@remote_window = @remote_window_limit
|
93
100
|
|
94
|
-
@recv_buffer =
|
95
|
-
@send_buffer = []
|
101
|
+
@recv_buffer = "".b
|
96
102
|
@continuation = []
|
97
103
|
@error = nil
|
98
104
|
|
99
105
|
@h2c_upgrade = nil
|
100
106
|
@closed_since = nil
|
107
|
+
@received_frame = false
|
101
108
|
end
|
102
109
|
|
103
110
|
def closed?
|
@@ -110,10 +117,14 @@ module HTTP2
|
|
110
117
|
# @param window [Integer]
|
111
118
|
# @param parent [Stream]
|
112
119
|
def new_stream(**args)
|
113
|
-
|
114
|
-
|
120
|
+
raise ConnectionClosed if @state == :closed
|
121
|
+
raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
|
122
|
+
|
123
|
+
connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_activated_stream
|
115
124
|
|
116
125
|
stream = activate_stream(id: @stream_id, **args)
|
126
|
+
@last_activated_stream = stream.id
|
127
|
+
|
117
128
|
@stream_id += 2
|
118
129
|
|
119
130
|
stream
|
@@ -140,15 +151,15 @@ module HTTP2
|
|
140
151
|
# @param payload [String]
|
141
152
|
def goaway(error = :no_error, payload = nil)
|
142
153
|
last_stream = if (max = @streams.max)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
154
|
+
max.first
|
155
|
+
else
|
156
|
+
0
|
157
|
+
end
|
147
158
|
|
148
159
|
send(type: :goaway, last_stream: last_stream,
|
149
160
|
error: error, payload: payload)
|
150
161
|
@state = :closed
|
151
|
-
@closed_since =
|
162
|
+
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
152
163
|
end
|
153
164
|
|
154
165
|
# Sends a WINDOW_UPDATE frame to the peer.
|
@@ -165,7 +176,7 @@ module HTTP2
|
|
165
176
|
# @param settings [Array or Hash]
|
166
177
|
def settings(payload)
|
167
178
|
payload = payload.to_a
|
168
|
-
|
179
|
+
validate_settings(@local_role, payload)
|
169
180
|
@pending_settings << payload
|
170
181
|
send(type: :settings, stream: 0, payload: payload)
|
171
182
|
@pending_settings << payload
|
@@ -188,33 +199,56 @@ module HTTP2
|
|
188
199
|
# SETTINGS frame. Server connection header is SETTINGS frame only.
|
189
200
|
if @state == :waiting_magic
|
190
201
|
if @recv_buffer.size < 24
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
return # maybe next time
|
195
|
-
end
|
202
|
+
raise HandshakeError unless CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
|
203
|
+
|
204
|
+
return # maybe next time
|
196
205
|
elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
|
197
206
|
# MAGIC is OK. Send our settings
|
198
207
|
@state = :waiting_connection_preface
|
199
208
|
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
200
209
|
settings(payload)
|
201
210
|
else
|
202
|
-
|
211
|
+
raise HandshakeError
|
203
212
|
end
|
204
213
|
end
|
205
214
|
|
206
215
|
while (frame = @framer.parse(@recv_buffer))
|
216
|
+
if is_a?(Client) && !@received_frame
|
217
|
+
connection_error(:protocol_error, msg: "didn't receive settings") if frame[:type] != :settings
|
218
|
+
@received_frame = true
|
219
|
+
end
|
220
|
+
|
221
|
+
# Implementations MUST discard frames
|
222
|
+
# that have unknown or unsupported types.
|
223
|
+
if frame[:type].nil?
|
224
|
+
# However, extension frames that appear in
|
225
|
+
# the middle of a header block (Section 4.3) are not permitted; these
|
226
|
+
# MUST be treated as a connection error (Section 5.4.1) of type
|
227
|
+
# PROTOCOL_ERROR.
|
228
|
+
connection_error(:protocol_error) unless @continuation.empty?
|
229
|
+
next
|
230
|
+
end
|
231
|
+
|
207
232
|
emit(:frame_received, frame)
|
208
233
|
|
209
234
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
210
235
|
# with no interleaved frames of any other type, or from any other stream.
|
211
236
|
unless @continuation.empty?
|
212
|
-
unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
|
213
|
-
connection_error
|
214
|
-
end
|
237
|
+
connection_error unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
|
215
238
|
|
216
239
|
@continuation << frame
|
217
|
-
|
240
|
+
unless frame[:flags].include? :end_headers
|
241
|
+
buffered_payload = @continuation.sum { |f| f[:payload].bytesize }
|
242
|
+
# prevent HTTP/2 CONTINUATION FLOOD
|
243
|
+
# same heuristic as the one from HAProxy: https://www.haproxy.com/blog/haproxy-is-resilient-to-the-http-2-continuation-flood
|
244
|
+
# different mitigation (connection closed, instead of 400 response)
|
245
|
+
unless buffered_payload < @local_settings[:settings_max_frame_size]
|
246
|
+
connection_error(:protocol_error,
|
247
|
+
msg: "too many continuations received")
|
248
|
+
end
|
249
|
+
|
250
|
+
next
|
251
|
+
end
|
218
252
|
|
219
253
|
payload = @continuation.map { |f| f[:payload] }.join
|
220
254
|
|
@@ -222,7 +256,7 @@ module HTTP2
|
|
222
256
|
@continuation.clear
|
223
257
|
|
224
258
|
frame.delete(:length)
|
225
|
-
frame[:payload] =
|
259
|
+
frame[:payload] = payload
|
226
260
|
frame[:flags] << :end_headers
|
227
261
|
end
|
228
262
|
|
@@ -232,19 +266,20 @@ module HTTP2
|
|
232
266
|
# anything other than 0x0, the endpoint MUST respond with a connection
|
233
267
|
# error (Section 5.4.1) of type PROTOCOL_ERROR.
|
234
268
|
if connection_frame?(frame)
|
269
|
+
connection_error(:protocol_error) unless frame[:stream].zero?
|
235
270
|
connection_management(frame)
|
236
271
|
else
|
237
272
|
case frame[:type]
|
238
273
|
when :headers
|
239
274
|
# When server receives even-numbered stream identifier,
|
240
275
|
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
|
241
|
-
connection_error if frame[:stream].even? &&
|
276
|
+
connection_error if frame[:stream].even? && is_a?(Server)
|
242
277
|
|
243
278
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
244
279
|
# frames MUST have the END_HEADERS flag set.
|
245
280
|
unless frame[:flags].include? :end_headers
|
246
281
|
@continuation << frame
|
247
|
-
|
282
|
+
next
|
248
283
|
end
|
249
284
|
|
250
285
|
# After sending a GOAWAY frame, the sender can discard frames
|
@@ -257,12 +292,15 @@ module HTTP2
|
|
257
292
|
|
258
293
|
stream = @streams[frame[:stream]]
|
259
294
|
if stream.nil?
|
295
|
+
verify_pseudo_headers(frame)
|
296
|
+
|
260
297
|
stream = activate_stream(
|
261
|
-
id:
|
262
|
-
weight:
|
298
|
+
id: frame[:stream],
|
299
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
263
300
|
dependency: frame[:dependency] || 0,
|
264
|
-
exclusive:
|
301
|
+
exclusive: frame[:exclusive] || false
|
265
302
|
)
|
303
|
+
verify_stream_order(stream.id)
|
266
304
|
emit(:stream, stream)
|
267
305
|
end
|
268
306
|
|
@@ -298,7 +336,7 @@ module HTTP2
|
|
298
336
|
return
|
299
337
|
end
|
300
338
|
|
301
|
-
connection_error(msg:
|
339
|
+
connection_error(msg: "missing parent ID") if parent.nil?
|
302
340
|
|
303
341
|
unless parent.state == :open || parent.state == :half_closed_local
|
304
342
|
# An endpoint might receive a PUSH_PROMISE frame after it sends
|
@@ -315,7 +353,9 @@ module HTTP2
|
|
315
353
|
end
|
316
354
|
end
|
317
355
|
|
356
|
+
_verify_pseudo_headers(frame, REQUEST_MANDATORY_HEADERS)
|
318
357
|
stream = activate_stream(id: pid, parent: parent)
|
358
|
+
verify_stream_order(stream.id)
|
319
359
|
emit(:promise, stream)
|
320
360
|
stream << frame
|
321
361
|
else
|
@@ -333,10 +373,10 @@ module HTTP2
|
|
333
373
|
# unused or closed parent stream.
|
334
374
|
when :priority
|
335
375
|
stream = activate_stream(
|
336
|
-
id:
|
337
|
-
weight:
|
376
|
+
id: frame[:stream],
|
377
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
338
378
|
dependency: frame[:dependency] || 0,
|
339
|
-
exclusive:
|
379
|
+
exclusive: frame[:exclusive] || false
|
340
380
|
)
|
341
381
|
|
342
382
|
emit(:stream, stream)
|
@@ -348,24 +388,26 @@ module HTTP2
|
|
348
388
|
# "closed" stream. A receiver MUST NOT treat this as an error
|
349
389
|
# (see Section 5.1).
|
350
390
|
when :window_update
|
351
|
-
|
391
|
+
stream = @streams_recently_closed[frame[:stream]]
|
392
|
+
connection_error(:protocol_error, msg: "sent window update on idle stream") unless stream
|
393
|
+
process_window_update(frame: frame, encode: true)
|
352
394
|
else
|
353
395
|
# An endpoint that receives an unexpected stream identifier
|
354
396
|
# MUST respond with a connection error of type PROTOCOL_ERROR.
|
355
|
-
connection_error
|
397
|
+
connection_error(msg: "stream does not exist")
|
356
398
|
end
|
357
399
|
end
|
358
400
|
end
|
359
401
|
end
|
360
402
|
end
|
361
|
-
|
362
403
|
rescue StandardError => e
|
363
404
|
raise if e.is_a?(Error::Error)
|
405
|
+
|
364
406
|
connection_error(e: e)
|
365
407
|
end
|
366
408
|
|
367
|
-
def <<(
|
368
|
-
receive(
|
409
|
+
def <<(data)
|
410
|
+
receive(data)
|
369
411
|
end
|
370
412
|
|
371
413
|
private
|
@@ -381,17 +423,16 @@ module HTTP2
|
|
381
423
|
if frame[:type] == :data
|
382
424
|
send_data(frame, true)
|
383
425
|
|
384
|
-
|
426
|
+
elsif frame[:type] == :rst_stream && frame[:error] == :protocol_error
|
385
427
|
# An endpoint can end a connection at any time. In particular, an
|
386
428
|
# endpoint MAY choose to treat a stream error as a connection error.
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
end
|
429
|
+
|
430
|
+
goaway(frame[:error])
|
431
|
+
else
|
432
|
+
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
433
|
+
# RST_STREAM that are not protocol errors
|
434
|
+
frames = encode(frame)
|
435
|
+
frames.each { |f| emit(:frame, f) }
|
395
436
|
end
|
396
437
|
end
|
397
438
|
|
@@ -401,10 +442,10 @@ module HTTP2
|
|
401
442
|
# @return [Array of Buffer] encoded frame
|
402
443
|
def encode(frame)
|
403
444
|
frames = if frame[:type] == :headers || frame[:type] == :push_promise
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
445
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
446
|
+
else
|
447
|
+
[frame] # otherwise one frame
|
448
|
+
end
|
408
449
|
|
409
450
|
frames.map { |f| @framer.generate(f) }
|
410
451
|
end
|
@@ -440,28 +481,26 @@ module HTTP2
|
|
440
481
|
when :settings
|
441
482
|
connection_settings(frame)
|
442
483
|
when :window_update
|
443
|
-
|
444
|
-
send_data(nil, true)
|
484
|
+
process_window_update(frame: frame, encode: true)
|
445
485
|
when :ping
|
446
|
-
|
447
|
-
emit(:ack, frame[:payload])
|
448
|
-
else
|
449
|
-
send(type: :ping, stream: 0,
|
450
|
-
flags: [:ack], payload: frame[:payload])
|
451
|
-
end
|
486
|
+
ping_management(frame)
|
452
487
|
when :goaway
|
453
488
|
# Receivers of a GOAWAY frame MUST NOT open additional streams on
|
454
489
|
# the connection, although a new connection can be established
|
455
490
|
# for new streams.
|
456
491
|
@state = :closed
|
457
|
-
@closed_since =
|
492
|
+
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
458
493
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
459
494
|
when :altsvc
|
460
495
|
# 4. The ALTSVC HTTP/2 Frame
|
461
496
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
462
497
|
# information is invalid and MUST be ignored.
|
463
|
-
if frame[:origin] && !frame[:origin].empty?
|
464
|
-
|
498
|
+
emit(frame[:type], frame) if frame[:origin] && !frame[:origin].empty?
|
499
|
+
when :origin
|
500
|
+
return if @h2c_upgrade || !frame[:flags].empty?
|
501
|
+
|
502
|
+
frame[:payload].each do |origin|
|
503
|
+
emit(frame[:type], origin)
|
465
504
|
end
|
466
505
|
when :blocked
|
467
506
|
emit(frame[:type], frame)
|
@@ -469,12 +508,28 @@ module HTTP2
|
|
469
508
|
connection_error
|
470
509
|
end
|
471
510
|
when :closed
|
472
|
-
|
511
|
+
case frame[:type]
|
512
|
+
when :goaway
|
513
|
+
connection_error
|
514
|
+
when :ping
|
515
|
+
ping_management(frame)
|
516
|
+
else
|
517
|
+
connection_error if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @closed_since) > 15
|
518
|
+
end
|
473
519
|
else
|
474
520
|
connection_error
|
475
521
|
end
|
476
522
|
end
|
477
523
|
|
524
|
+
def ping_management(frame)
|
525
|
+
if frame[:flags].include? :ack
|
526
|
+
emit(:ack, frame[:payload])
|
527
|
+
else
|
528
|
+
send(type: :ping, stream: 0,
|
529
|
+
flags: [:ack], payload: frame[:payload])
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
478
533
|
# Validate settings parameters. See sepc Section 6.5.2.
|
479
534
|
#
|
480
535
|
# @param role [Symbol] The sender's role: :client or :server
|
@@ -482,8 +537,6 @@ module HTTP2
|
|
482
537
|
def validate_settings(role, settings)
|
483
538
|
settings.each do |key, v|
|
484
539
|
case key
|
485
|
-
when :settings_header_table_size
|
486
|
-
# Any value is valid
|
487
540
|
when :settings_enable_push
|
488
541
|
case role
|
489
542
|
when :server
|
@@ -491,38 +544,41 @@ module HTTP2
|
|
491
544
|
# Clients MUST reject any attempt to change the
|
492
545
|
# SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
|
493
546
|
# message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
494
|
-
|
547
|
+
next if v.zero?
|
548
|
+
|
549
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
495
550
|
when :client
|
496
551
|
# Any value other than 0 or 1 MUST be treated as a
|
497
552
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
498
|
-
|
499
|
-
|
500
|
-
|
553
|
+
next if v.zero? || v == 1
|
554
|
+
|
555
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
501
556
|
end
|
502
|
-
when :settings_max_concurrent_streams
|
503
|
-
# Any value is valid
|
504
557
|
when :settings_initial_window_size
|
505
558
|
# Values above the maximum flow control window size of 2^31-1 MUST
|
506
559
|
# be treated as a connection error (Section 5.4.1) of type
|
507
560
|
# FLOW_CONTROL_ERROR.
|
508
|
-
|
509
|
-
|
510
|
-
|
561
|
+
next if v <= 0x7fffffff
|
562
|
+
|
563
|
+
connection_error(:flow_control_error, msg: "invalid #{key} value")
|
511
564
|
when :settings_max_frame_size
|
512
565
|
# The initial value is 2^14 (16,384) octets. The value advertised
|
513
566
|
# by an endpoint MUST be between this initial value and the maximum
|
514
567
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
515
568
|
# Values outside this range MUST be treated as a connection error
|
516
569
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
570
|
+
next if v >= 16_384 && v <= 16_777_215
|
571
|
+
|
572
|
+
connection_error(:protocol_error, msg: "invalid #{key} value")
|
573
|
+
# when :settings_max_concurrent_streams
|
574
|
+
# Any value is valid
|
575
|
+
# when :settings_header_table_size
|
576
|
+
# Any value is valid
|
577
|
+
# when :settings_max_header_list_size
|
521
578
|
# Any value is valid
|
522
579
|
# else # ignore unknown settings
|
523
580
|
end
|
524
581
|
end
|
525
|
-
nil
|
526
582
|
end
|
527
583
|
|
528
584
|
# Update connection settings based on parameters set by the peer.
|
@@ -536,12 +592,12 @@ module HTTP2
|
|
536
592
|
# local: previously sent and pended our settings should be effective
|
537
593
|
# remote: just received peer settings should immediately be effective
|
538
594
|
settings, side = if frame[:flags].include?(:ack)
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
595
|
+
# Process pending settings we have sent.
|
596
|
+
[@pending_settings.shift, :local]
|
597
|
+
else
|
598
|
+
validate_settings(@remote_role, frame[:payload])
|
599
|
+
[frame[:payload], :remote]
|
600
|
+
end
|
545
601
|
|
546
602
|
settings.each do |key, v|
|
547
603
|
case side
|
@@ -565,14 +621,17 @@ module HTTP2
|
|
565
621
|
case side
|
566
622
|
when :local
|
567
623
|
@local_window = @local_window - @local_window_limit + v
|
568
|
-
@streams.
|
624
|
+
@streams.each_value do |stream|
|
569
625
|
stream.emit(:local_window, stream.local_window - @local_window_limit + v)
|
570
626
|
end
|
571
627
|
|
572
628
|
@local_window_limit = v
|
573
629
|
when :remote
|
574
|
-
|
575
|
-
|
630
|
+
# can adjust the initial window size for new streams by including a
|
631
|
+
# value for SETTINGS_INITIAL_WINDOW_SIZE in the SETTINGS frame.
|
632
|
+
# The connection flow-control window can only be changed using
|
633
|
+
# WINDOW_UPDATE frames.
|
634
|
+
@streams.each_value do |stream|
|
576
635
|
# Event name is :window, not :remote_window
|
577
636
|
stream.emit(:window, stream.remote_window - @remote_window_limit + v)
|
578
637
|
end
|
@@ -593,8 +652,7 @@ module HTTP2
|
|
593
652
|
# nothing to do
|
594
653
|
|
595
654
|
when :settings_max_frame_size
|
596
|
-
|
597
|
-
@framer.max_frame_size = v
|
655
|
+
@framer.remote_max_frame_size = v
|
598
656
|
|
599
657
|
# else # ignore unknown settings
|
600
658
|
end
|
@@ -608,6 +666,9 @@ module HTTP2
|
|
608
666
|
unless @state == :closed || @h2c_upgrade == :start
|
609
667
|
# Send ack to peer
|
610
668
|
send(type: :settings, stream: 0, payload: [], flags: [:ack])
|
669
|
+
# when initial window size changes, we try to flush any buffered
|
670
|
+
# data.
|
671
|
+
@streams.each_value(&:flush)
|
611
672
|
end
|
612
673
|
end
|
613
674
|
end
|
@@ -621,10 +682,7 @@ module HTTP2
|
|
621
682
|
#
|
622
683
|
# @param frame [Hash]
|
623
684
|
def decode_headers(frame)
|
624
|
-
if frame[:payload].is_a?
|
625
|
-
frame[:payload] = @decompressor.decode(frame[:payload])
|
626
|
-
end
|
627
|
-
|
685
|
+
frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a?(String)
|
628
686
|
rescue CompressionError => e
|
629
687
|
connection_error(:compression_error, e: e)
|
630
688
|
rescue ProtocolError => e
|
@@ -639,30 +697,36 @@ module HTTP2
|
|
639
697
|
# @return [Array of Frame]
|
640
698
|
def encode_headers(frame)
|
641
699
|
payload = frame[:payload]
|
642
|
-
|
700
|
+
begin
|
701
|
+
payload = frame[:payload] = @compressor.encode(payload) unless payload.is_a?(String)
|
702
|
+
rescue StandardError => e
|
703
|
+
connection_error(:compression_error, e: e)
|
704
|
+
end
|
705
|
+
|
706
|
+
# if single frame, return immediately
|
707
|
+
return [frame] if payload.bytesize <= @remote_settings[:settings_max_frame_size]
|
643
708
|
|
644
709
|
frames = []
|
645
710
|
|
646
|
-
|
711
|
+
until payload.nil? || payload.empty?
|
647
712
|
cont = frame.dup
|
648
|
-
|
649
|
-
|
650
|
-
|
713
|
+
|
714
|
+
# first frame remains HEADERS
|
715
|
+
unless frames.empty?
|
716
|
+
cont[:type] = :continuation
|
717
|
+
cont[:flags] = EMPTY
|
718
|
+
end
|
719
|
+
|
720
|
+
cont[:payload] = payload.byteslice(0, @remote_settings[:settings_max_frame_size])
|
721
|
+
payload = payload.byteslice(@remote_settings[:settings_max_frame_size]..-1)
|
722
|
+
|
651
723
|
frames << cont
|
652
724
|
end
|
653
|
-
if frames.empty?
|
654
|
-
frames = [frame]
|
655
|
-
else
|
656
|
-
frames.first[:type] = frame[:type]
|
657
|
-
frames.first[:flags] = frame[:flags] - [:end_headers]
|
658
|
-
frames.last[:flags] << :end_headers
|
659
|
-
end
|
660
725
|
|
661
|
-
frames
|
726
|
+
frames.first[:flags].delete(:end_headers)
|
727
|
+
frames.last[:flags] = [:end_headers]
|
662
728
|
|
663
|
-
|
664
|
-
connection_error(:compression_error, e: e)
|
665
|
-
nil
|
729
|
+
frames
|
666
730
|
end
|
667
731
|
|
668
732
|
# Activates new incoming or outgoing stream and registers appropriate
|
@@ -672,46 +736,54 @@ module HTTP2
|
|
672
736
|
# @param priority [Integer]
|
673
737
|
# @param window [Integer]
|
674
738
|
# @param parent [Stream]
|
675
|
-
def activate_stream(id
|
676
|
-
connection_error(msg:
|
739
|
+
def activate_stream(id:, **args)
|
740
|
+
connection_error(msg: "Stream ID already exists") if @streams.key?(id)
|
677
741
|
|
678
|
-
|
742
|
+
raise StreamLimitExceeded if @active_stream_count >= @local_settings[:settings_max_concurrent_streams]
|
679
743
|
|
680
|
-
|
681
|
-
# states count toward the maximum number of streams that an endpoint is
|
682
|
-
# permitted to open.
|
683
|
-
stream.once(:active) { @active_stream_count += 1 }
|
684
|
-
stream.once(:close) do
|
685
|
-
@active_stream_count -= 1
|
744
|
+
stream = Stream.new(connection: self, id: id, **args)
|
686
745
|
|
746
|
+
stream.once(:close) do
|
687
747
|
# Store a reference to the closed stream, such that we can respond
|
688
748
|
# to any in-flight frames while close is registered on both sides.
|
689
749
|
# References to such streams will be purged whenever another stream
|
690
|
-
# is closed, with a
|
691
|
-
|
692
|
-
|
750
|
+
# is closed, with a minimum of 15s RTT time window.
|
751
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
752
|
+
|
753
|
+
# TODO: use a drop_while! variant whenever there is one.
|
754
|
+
@streams_recently_closed = @streams_recently_closed.drop_while do |_, v|
|
755
|
+
(now - v) > 15
|
756
|
+
end.to_h
|
757
|
+
|
758
|
+
@streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
693
759
|
end
|
694
760
|
|
695
|
-
stream.on(:promise, &method(:promise)) if
|
761
|
+
stream.on(:promise, &method(:promise)) if is_a? Server
|
696
762
|
stream.on(:frame, &method(:send))
|
697
763
|
|
698
764
|
@streams[id] = stream
|
699
765
|
end
|
700
766
|
|
701
|
-
|
702
|
-
|
703
|
-
now_ts = Time.now.to_i
|
704
|
-
to_delete = []
|
705
|
-
@streams_recently_closed.each do |stream_id, ts|
|
706
|
-
# Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
|
707
|
-
break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
|
708
|
-
to_delete << stream_id
|
709
|
-
end
|
767
|
+
def verify_stream_order(id)
|
768
|
+
return unless id.odd?
|
710
769
|
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
770
|
+
connection_error(msg: "Stream ID smaller than previous") if @last_stream_id > id
|
771
|
+
@last_stream_id = id
|
772
|
+
end
|
773
|
+
|
774
|
+
def _verify_pseudo_headers(frame, mandatory_headers)
|
775
|
+
headers = frame[:payload]
|
776
|
+
return if headers.is_a?(String)
|
777
|
+
|
778
|
+
pseudo_headers = headers.take_while do |field, value|
|
779
|
+
# use this loop to validate pseudo-headers
|
780
|
+
connection_error(:protocol_error, msg: "path is empty") if field == ":path" && value.empty?
|
781
|
+
field.start_with?(":")
|
782
|
+
end.map(&:first)
|
783
|
+
return if mandatory_headers.size == pseudo_headers.size &&
|
784
|
+
(mandatory_headers - pseudo_headers).empty?
|
785
|
+
|
786
|
+
connection_error(:protocol_error, msg: "invalid pseudo-headers")
|
715
787
|
end
|
716
788
|
|
717
789
|
# Emit GOAWAY error indicating to peer that the connection is being
|
@@ -728,11 +800,11 @@ module HTTP2
|
|
728
800
|
def connection_error(error = :protocol_error, msg: nil, e: nil)
|
729
801
|
goaway(error) unless @state == :closed || @state == :new
|
730
802
|
|
731
|
-
@state
|
732
|
-
|
733
|
-
msg ||= e
|
734
|
-
backtrace =
|
735
|
-
|
803
|
+
@state = :closed
|
804
|
+
@error = error
|
805
|
+
msg ||= e ? e.message : "protocol error"
|
806
|
+
backtrace = e ? e.backtrace : nil
|
807
|
+
raise Error.types[error], msg, backtrace
|
736
808
|
end
|
737
809
|
alias error connection_error
|
738
810
|
|
@@ -740,5 +812,5 @@ module HTTP2
|
|
740
812
|
yield
|
741
813
|
end
|
742
814
|
end
|
743
|
-
# rubocop:enable ClassLength
|
815
|
+
# rubocop:enable Metrics/ClassLength
|
744
816
|
end
|