protocol-http2 0.3.0 → 0.4.1
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/lib/protocol/http2/client.rb +4 -0
- data/lib/protocol/http2/connection.rb +56 -39
- data/lib/protocol/http2/continuation_frame.rb +19 -2
- data/lib/protocol/http2/error.rb +6 -0
- data/lib/protocol/http2/flow_control.rb +10 -2
- data/lib/protocol/http2/frame.rb +14 -2
- data/lib/protocol/http2/framer.rb +3 -2
- data/lib/protocol/http2/ping_frame.rb +2 -2
- data/lib/protocol/http2/server.rb +4 -0
- data/lib/protocol/http2/settings_frame.rb +5 -3
- data/lib/protocol/http2/stream.rb +22 -14
- data/lib/protocol/http2/version.rb +1 -1
- data/lib/protocol/http2/window_update_frame.rb +5 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59f70b2d8905a24deb2b5f6400347631c226dea2814f23a0701d64ca343c8e7e
|
4
|
+
data.tar.gz: b6370e82f47701f4eb7ad21049524cf559d7c1303a839a802a88dfc8f2808c16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2c32cb5174ed643a415b836a2faa53a325a5e3252e1db55be9fafbc117fd78beb3768b467879f27b146da001caf3a92c74137924e5cf4c9eb86006b7d5a4a42
|
7
|
+
data.tar.gz: 05a417ab4821acb8e0db930440f8a7583ae265f1dd14363eea42963cc4fb313e413aa91d3c81ca4453a413f5db42ef6934e6c4d76cef5f9ac7b757f5f7c6a67e
|
@@ -50,6 +50,7 @@ module Protocol
|
|
50
50
|
0
|
51
51
|
end
|
52
52
|
|
53
|
+
# The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting.
|
53
54
|
def maximum_frame_size
|
54
55
|
@remote_settings.maximum_frame_size
|
55
56
|
end
|
@@ -78,11 +79,15 @@ module Protocol
|
|
78
79
|
@state == :closed
|
79
80
|
end
|
80
81
|
|
81
|
-
def
|
82
|
+
def connection_error!(error, message)
|
82
83
|
return if @framer.closed?
|
83
84
|
|
84
|
-
send_goaway
|
85
|
+
send_goaway(error, message)
|
85
86
|
|
87
|
+
self.close
|
88
|
+
end
|
89
|
+
|
90
|
+
def close
|
86
91
|
@framer.close
|
87
92
|
end
|
88
93
|
|
@@ -117,16 +122,21 @@ module Protocol
|
|
117
122
|
frame.apply(self)
|
118
123
|
|
119
124
|
return frame
|
125
|
+
rescue GoawayError
|
126
|
+
# This is not a connection error. We are done.
|
127
|
+
self.close
|
128
|
+
|
129
|
+
raise
|
120
130
|
rescue ProtocolError => error
|
121
|
-
|
131
|
+
connection_error!(error.code || PROTOCOL_ERROR, error.message)
|
122
132
|
|
123
133
|
raise
|
124
134
|
rescue HPACK::CompressionError => error
|
125
|
-
|
135
|
+
connection_error!(COMPRESSION_ERROR, error.message)
|
126
136
|
|
127
137
|
raise
|
128
138
|
rescue
|
129
|
-
|
139
|
+
connection_error!(PROTOCOL_ERROR, $!.message)
|
130
140
|
|
131
141
|
raise
|
132
142
|
end
|
@@ -236,6 +246,10 @@ module Protocol
|
|
236
246
|
|
237
247
|
def receive_ping(frame)
|
238
248
|
if @state != :closed
|
249
|
+
if frame.stream_id != 0
|
250
|
+
raise ProtocolError, "Ping received for non-zero stream!"
|
251
|
+
end
|
252
|
+
|
239
253
|
unless frame.acknowledgement?
|
240
254
|
reply = frame.acknowledge
|
241
255
|
|
@@ -251,17 +265,32 @@ module Protocol
|
|
251
265
|
|
252
266
|
if stream = @streams[frame.stream_id]
|
253
267
|
stream.receive_data(frame)
|
254
|
-
|
255
|
-
if stream.closed?
|
256
|
-
@streams.delete(stream.id)
|
257
|
-
end
|
258
268
|
else
|
259
|
-
raise ProtocolError, "
|
269
|
+
raise ProtocolError, "Cannot receive data for idle stream #{frame.stream_id}"
|
260
270
|
end
|
261
271
|
end
|
262
272
|
|
273
|
+
def valid_remote_stream_id?
|
274
|
+
false
|
275
|
+
end
|
276
|
+
|
277
|
+
# Accept a stream from the other side of the connnection.
|
278
|
+
def accept_stream(stream_id)
|
279
|
+
unless valid_remote_stream_id?(stream_id)
|
280
|
+
raise ProtocolError, "Invalid remote stream id: #{stream_id}"
|
281
|
+
end
|
282
|
+
|
283
|
+
if stream_id <= @remote_stream_id
|
284
|
+
raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
|
285
|
+
end
|
286
|
+
|
287
|
+
@remote_stream_id = stream_id
|
288
|
+
create_stream(stream_id)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Create a stream on this side of the connection.
|
263
292
|
def create_stream(stream_id = next_stream_id)
|
264
|
-
Stream.new(self, stream_id)
|
293
|
+
@streams[stream_id] = Stream.new(self, stream_id)
|
265
294
|
end
|
266
295
|
|
267
296
|
def receive_headers(frame)
|
@@ -271,49 +300,30 @@ module Protocol
|
|
271
300
|
|
272
301
|
if stream = @streams[frame.stream_id]
|
273
302
|
stream.receive_headers(frame)
|
274
|
-
|
275
|
-
if stream.closed?
|
276
|
-
@streams.delete(stream.id)
|
277
|
-
end
|
278
|
-
elsif frame.stream_id > @remote_stream_id
|
303
|
+
else
|
279
304
|
if @streams.count < self.maximum_concurrent_streams
|
280
|
-
stream =
|
305
|
+
stream = accept_stream(frame.stream_id)
|
281
306
|
stream.receive_headers(frame)
|
282
|
-
|
283
|
-
@remote_stream_id = stream.id
|
284
|
-
@streams[stream.id] = stream
|
285
307
|
else
|
286
308
|
raise ProtocolError, "Exceeded maximum concurrent streams"
|
287
309
|
end
|
288
310
|
end
|
289
311
|
end
|
290
312
|
|
291
|
-
def deleted_stream? frame
|
292
|
-
frame.stream_id <= @local_stream_id or frame.stream_id <= @remote_stream_id
|
293
|
-
end
|
294
|
-
|
295
313
|
def receive_priority(frame)
|
296
314
|
if stream = @streams[frame.stream_id]
|
297
315
|
stream.receive_priority(frame)
|
298
|
-
elsif deleted_stream? frame
|
299
|
-
# ignore
|
300
316
|
else
|
301
|
-
stream =
|
317
|
+
stream = accept_stream(frame.stream_id)
|
302
318
|
stream.receive_priority(frame)
|
303
|
-
|
304
|
-
@streams[frame.stream_id] = stream
|
305
319
|
end
|
306
320
|
end
|
307
321
|
|
308
322
|
def receive_reset_stream(frame)
|
309
323
|
if stream = @streams[frame.stream_id]
|
310
324
|
stream.receive_reset_stream(frame)
|
311
|
-
|
312
|
-
@streams.delete(stream.id)
|
313
|
-
elsif deleted_stream? frame
|
314
|
-
# ignore
|
315
325
|
else
|
316
|
-
raise
|
326
|
+
raise StreamClosed, "Cannot reset stream #{frame.stream_id}"
|
317
327
|
end
|
318
328
|
end
|
319
329
|
|
@@ -321,11 +331,14 @@ module Protocol
|
|
321
331
|
if frame.connection?
|
322
332
|
super
|
323
333
|
elsif stream = @streams[frame.stream_id]
|
324
|
-
|
325
|
-
|
326
|
-
|
334
|
+
begin
|
335
|
+
stream.receive_window_update(frame)
|
336
|
+
rescue ProtocolError => error
|
337
|
+
stream.send_reset_stream(error.code)
|
338
|
+
end
|
327
339
|
else
|
328
|
-
|
340
|
+
# Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be treated as a connection error of type PROTOCOL_ERROR.
|
341
|
+
raise ProtocolError, "Cannot update window of idle stream #{frame.stream_id}"
|
329
342
|
end
|
330
343
|
end
|
331
344
|
|
@@ -336,8 +349,12 @@ module Protocol
|
|
336
349
|
end
|
337
350
|
end
|
338
351
|
|
352
|
+
def receive_continuation(frame)
|
353
|
+
raise ProtocolError, "Received unexpected continuation: #{frame.class}"
|
354
|
+
end
|
355
|
+
|
339
356
|
def receive_frame(frame)
|
340
|
-
|
357
|
+
# ignore.
|
341
358
|
end
|
342
359
|
end
|
343
360
|
end
|
@@ -37,9 +37,21 @@ module Protocol
|
|
37
37
|
super
|
38
38
|
|
39
39
|
unless end_headers?
|
40
|
-
|
40
|
+
continuation = ContinuationFrame.new
|
41
|
+
continuation.read_header(stream)
|
42
|
+
|
43
|
+
# We validate the frame type here:
|
44
|
+
unless continuation.valid_type?
|
45
|
+
raise ProtocolError, "Invalid frame type: #{@type}!"
|
46
|
+
end
|
47
|
+
|
48
|
+
if continuation.stream_id != @stream_id
|
49
|
+
raise ProtocolError, "Invalid stream id: #{continuation.stream_id} for continuation of stream id: #{@stream_id}!"
|
50
|
+
end
|
41
51
|
|
42
|
-
|
52
|
+
continuation.read(stream, maximum_frame_size)
|
53
|
+
|
54
|
+
@continuation = continuation
|
43
55
|
end
|
44
56
|
end
|
45
57
|
|
@@ -93,6 +105,11 @@ module Protocol
|
|
93
105
|
include Continued
|
94
106
|
|
95
107
|
TYPE = 0x9
|
108
|
+
|
109
|
+
# This is only invoked if the continuation is received out of the normal flow.
|
110
|
+
def apply(connection)
|
111
|
+
connection.receive_continuation(self)
|
112
|
+
end
|
96
113
|
end
|
97
114
|
end
|
98
115
|
end
|
data/lib/protocol/http2/error.rb
CHANGED
@@ -27,6 +27,8 @@ module Protocol
|
|
27
27
|
maximum_frame_size = self.maximum_frame_size
|
28
28
|
available_size = @remote_window.available
|
29
29
|
|
30
|
+
# puts "available_size=#{available_size} maximum_frame_size=#{maximum_frame_size}"
|
31
|
+
|
30
32
|
if available_size < maximum_frame_size
|
31
33
|
return available_size
|
32
34
|
else
|
@@ -71,8 +73,14 @@ module Protocol
|
|
71
73
|
def receive_window_update(frame)
|
72
74
|
was_full = @remote_window.full?
|
73
75
|
|
74
|
-
|
75
|
-
@remote_window
|
76
|
+
amount = frame.unpack
|
77
|
+
# puts "expand remote_window=#{@remote_window} by #{amount}"
|
78
|
+
|
79
|
+
if amount != 0
|
80
|
+
@remote_window.expand(amount)
|
81
|
+
else
|
82
|
+
raise ProtocolError, "Invalid window size increment: #{amount}!"
|
83
|
+
end
|
76
84
|
|
77
85
|
self.window_updated if was_full
|
78
86
|
end
|
data/lib/protocol/http2/frame.rb
CHANGED
@@ -29,6 +29,7 @@ module Protocol
|
|
29
29
|
PRIORITY = 0x20
|
30
30
|
|
31
31
|
MAXIMUM_ALLOWED_WINDOW_SIZE = 0x7FFFFFFF
|
32
|
+
MINIMUM_ALLOWED_FRAME_SIZE = 0x4000
|
32
33
|
MAXIMUM_ALLOWED_FRAME_SIZE = 0xFFFFFF
|
33
34
|
|
34
35
|
class Frame
|
@@ -46,7 +47,7 @@ module Protocol
|
|
46
47
|
LENGTH_LOMASK = 0xFFFF
|
47
48
|
|
48
49
|
# @param length [Integer] the length of the payload, or nil if the header has not been read yet.
|
49
|
-
def initialize(stream_id = 0, flags = 0, type = self.class
|
50
|
+
def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
|
50
51
|
@length = length
|
51
52
|
@type = type
|
52
53
|
@flags = flags
|
@@ -54,6 +55,10 @@ module Protocol
|
|
54
55
|
@payload = payload
|
55
56
|
end
|
56
57
|
|
58
|
+
def valid_type?
|
59
|
+
@type == self.class::TYPE
|
60
|
+
end
|
61
|
+
|
57
62
|
def <=> other
|
58
63
|
to_ary <=> other.to_ary
|
59
64
|
end
|
@@ -147,11 +152,18 @@ module Protocol
|
|
147
152
|
length = (length_hi << LENGTH_HISHIFT) | length_lo
|
148
153
|
stream_id = stream_id & STREAM_ID_MASK
|
149
154
|
|
155
|
+
# puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}"
|
156
|
+
|
150
157
|
return length, type, flags, stream_id
|
151
158
|
end
|
152
159
|
|
153
160
|
def read_header(stream)
|
154
|
-
|
161
|
+
if buffer = stream.read(9)
|
162
|
+
@length, @type, @flags, @stream_id = Frame.parse_header(buffer)
|
163
|
+
# puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}"
|
164
|
+
else
|
165
|
+
raise EOFError, "Could not read frame header!"
|
166
|
+
end
|
155
167
|
end
|
156
168
|
|
157
169
|
def read_payload(stream)
|
@@ -82,11 +82,12 @@ module Protocol
|
|
82
82
|
def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
|
83
83
|
# Read the header:
|
84
84
|
length, type, flags, stream_id = read_header
|
85
|
-
|
86
|
-
# puts "framer: read_frame #{type} #{length}"
|
85
|
+
# puts "read_frame: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id} -> klass=#{@frames[type].inspect}"
|
87
86
|
|
88
87
|
# Allocate the frame:
|
89
88
|
klass = @frames[type] || Frame
|
89
|
+
# puts "read_frame #{klass} id=#{stream_id} length=#{length} flags=#{flags}"
|
90
|
+
|
90
91
|
frame = klass.new(stream_id, flags, type, length)
|
91
92
|
|
92
93
|
# Read the payload:
|
@@ -68,10 +68,12 @@ module Protocol
|
|
68
68
|
attr :maximum_frame_size
|
69
69
|
|
70
70
|
def maximum_frame_size= value
|
71
|
-
if value
|
72
|
-
@maximum_frame_size = value
|
73
|
-
else
|
71
|
+
if value > MAXIMUM_ALLOWED_FRAME_SIZE
|
74
72
|
raise ProtocolError, "Invalid value for maximum_frame_size: #{value} > #{MAXIMUM_ALLOWED_FRAME_SIZE}"
|
73
|
+
elsif value < MINIMUM_ALLOWED_FRAME_SIZE
|
74
|
+
raise ProtocolError, "Invalid value for maximum_frame_size: #{value} < #{MINIMUM_ALLOWED_FRAME_SIZE}"
|
75
|
+
else
|
76
|
+
@maximum_frame_size = value
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -77,8 +77,6 @@ module Protocol
|
|
77
77
|
@connection = connection
|
78
78
|
@id = id
|
79
79
|
|
80
|
-
@connection.streams[@id] = self
|
81
|
-
|
82
80
|
@state = :idle
|
83
81
|
|
84
82
|
@priority = nil
|
@@ -101,6 +99,14 @@ module Protocol
|
|
101
99
|
attr :local_window
|
102
100
|
attr :remote_window
|
103
101
|
|
102
|
+
def priority= priority
|
103
|
+
if priority.stream_id == @id
|
104
|
+
raise ProtocolError, "Stream #{@id} cannot depend on itself!"
|
105
|
+
end
|
106
|
+
|
107
|
+
@priority = priority
|
108
|
+
end
|
109
|
+
|
104
110
|
def maximum_frame_size
|
105
111
|
@connection.available_frame_size
|
106
112
|
end
|
@@ -141,7 +147,7 @@ module Protocol
|
|
141
147
|
return frame
|
142
148
|
end
|
143
149
|
|
144
|
-
#The HEADERS frame is used to open a stream, and additionally carries a header block fragment.
|
150
|
+
# The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state.
|
145
151
|
def send_headers(*args)
|
146
152
|
if @state == :idle
|
147
153
|
frame = write_headers(*args)
|
@@ -165,7 +171,7 @@ module Protocol
|
|
165
171
|
frame = write_headers(*args)
|
166
172
|
|
167
173
|
if frame.end_stream?
|
168
|
-
close
|
174
|
+
close
|
169
175
|
end
|
170
176
|
else
|
171
177
|
raise ProtocolError, "Cannot send headers in state: #{@state}"
|
@@ -201,17 +207,15 @@ module Protocol
|
|
201
207
|
frame = write_data(*args)
|
202
208
|
|
203
209
|
if frame.end_stream?
|
204
|
-
close
|
210
|
+
close
|
205
211
|
end
|
206
212
|
else
|
207
213
|
raise ProtocolError, "Cannot send data in state: #{@state}"
|
208
214
|
end
|
209
215
|
end
|
210
216
|
|
211
|
-
def close
|
217
|
+
def close(state = :closed)
|
212
218
|
@state = state
|
213
|
-
|
214
|
-
@connection.streams.delete(@id)
|
215
219
|
end
|
216
220
|
|
217
221
|
def send_reset_stream(error_code = 0)
|
@@ -221,7 +225,7 @@ module Protocol
|
|
221
225
|
|
222
226
|
write_frame(frame)
|
223
227
|
|
224
|
-
close
|
228
|
+
close(:reset)
|
225
229
|
else
|
226
230
|
raise ProtocolError, "Cannot reset stream in state: #{@state}"
|
227
231
|
end
|
@@ -232,7 +236,7 @@ module Protocol
|
|
232
236
|
priority, data = frame.unpack
|
233
237
|
|
234
238
|
if priority
|
235
|
-
|
239
|
+
self.priority = priority
|
236
240
|
end
|
237
241
|
|
238
242
|
@connection.decode_headers(data)
|
@@ -259,7 +263,7 @@ module Protocol
|
|
259
263
|
@headers = process_headers(frame)
|
260
264
|
elsif @state == :half_closed_local
|
261
265
|
if frame.end_stream?
|
262
|
-
close
|
266
|
+
close
|
263
267
|
end
|
264
268
|
|
265
269
|
@headers = process_headers(frame)
|
@@ -284,7 +288,7 @@ module Protocol
|
|
284
288
|
consume_local_window(frame)
|
285
289
|
|
286
290
|
if frame.end_stream?
|
287
|
-
close
|
291
|
+
close
|
288
292
|
end
|
289
293
|
|
290
294
|
@data = frame.unpack
|
@@ -296,12 +300,12 @@ module Protocol
|
|
296
300
|
end
|
297
301
|
|
298
302
|
def receive_priority(frame)
|
299
|
-
|
303
|
+
self.priority = frame.unpack
|
300
304
|
end
|
301
305
|
|
302
306
|
def receive_reset_stream(frame)
|
303
307
|
if @state != :idle and @state != :closed
|
304
|
-
close
|
308
|
+
close
|
305
309
|
|
306
310
|
return frame.unpack
|
307
311
|
else
|
@@ -367,6 +371,10 @@ module Protocol
|
|
367
371
|
|
368
372
|
return stream, headers
|
369
373
|
end
|
374
|
+
|
375
|
+
def inspect
|
376
|
+
"\#<#{self.class} id=#{@id} state=#{@state}>"
|
377
|
+
end
|
370
378
|
end
|
371
379
|
end
|
372
380
|
end
|
@@ -31,8 +31,6 @@ module Protocol
|
|
31
31
|
# These two fields are primarily used for efficiently sending window updates:
|
32
32
|
@used = 0
|
33
33
|
@capacity = capacity
|
34
|
-
|
35
|
-
fail unless capacity
|
36
34
|
end
|
37
35
|
|
38
36
|
def dup
|
@@ -41,7 +39,7 @@ module Protocol
|
|
41
39
|
|
42
40
|
# The window is completely full?
|
43
41
|
def full?
|
44
|
-
@available
|
42
|
+
@available <= 0
|
45
43
|
end
|
46
44
|
|
47
45
|
attr :used
|
@@ -67,6 +65,10 @@ module Protocol
|
|
67
65
|
def expand(amount)
|
68
66
|
@available += amount
|
69
67
|
@used -= amount
|
68
|
+
|
69
|
+
if @available > MAXIMUM_ALLOWED_WINDOW_SIZE
|
70
|
+
raise FlowControlError, "Expanding window by #{amount} caused overflow: #{@available} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}!"
|
71
|
+
end
|
70
72
|
end
|
71
73
|
|
72
74
|
def limited?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-http2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: protocol-hpack
|
@@ -150,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
150
|
- !ruby/object:Gem::Version
|
151
151
|
version: '0'
|
152
152
|
requirements: []
|
153
|
-
rubygems_version: 3.0.
|
153
|
+
rubygems_version: 3.0.3
|
154
154
|
signing_key:
|
155
155
|
specification_version: 4
|
156
156
|
summary: A low level implementation of the HTTP/2 protocol.
|