http-2 0.11.0 → 1.0.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.
- 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
|