protocol-http2 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|