protocol-http2 0.6.0 → 0.7.0
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 +1 -1
- data/lib/protocol/http2/connection.rb +62 -35
- data/lib/protocol/http2/continuation_frame.rb +4 -0
- data/lib/protocol/http2/data_frame.rb +4 -0
- data/lib/protocol/http2/error.rb +7 -1
- data/lib/protocol/http2/flow_control.rb +40 -8
- data/lib/protocol/http2/frame.rb +4 -0
- data/lib/protocol/http2/framer.rb +7 -3
- data/lib/protocol/http2/headers_frame.rb +4 -0
- data/lib/protocol/http2/priority_frame.rb +18 -2
- data/lib/protocol/http2/server.rb +1 -1
- data/lib/protocol/http2/settings_frame.rb +7 -2
- data/lib/protocol/http2/stream.rb +133 -42
- data/lib/protocol/http2/version.rb +1 -1
- data/lib/protocol/http2/window_update_frame.rb +2 -0
- data/protocol-http2.gemspec +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49e091bc1a6d2b2bdee4176a13648d306f81042ebc6d25cf0a44a921d33d0950
|
4
|
+
data.tar.gz: 5ecd3a8454c2a130247c624abdd067d87b38e63c09e9ed88fe2214e52461ac6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca124b2c3232e5329b4dd19c29f1b503fb490a03d94b1501f03de0ca1f478fbed8a99c38318002eda6b7f2df484be0b8ab9b1ed0c38a4388a7e63ed2065a5d2f
|
7
|
+
data.tar.gz: e766f4a80429e0ddc22edc2700ce4d20054d4fc07de7d81677cb1b89351019c5bf565a5ff51ce224002d0bb3e9184cbdda97807c2d59013d34c67fab31421443
|
@@ -40,7 +40,7 @@ module Protocol
|
|
40
40
|
yield if block_given?
|
41
41
|
|
42
42
|
read_frame do |frame|
|
43
|
-
raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
43
|
+
raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
44
44
|
end
|
45
45
|
else
|
46
46
|
raise ProtocolError, "Cannot send connection preface in state #{@state}"
|
@@ -29,8 +29,11 @@ module Protocol
|
|
29
29
|
include FlowControl
|
30
30
|
|
31
31
|
def initialize(framer, local_stream_id)
|
32
|
+
super()
|
33
|
+
|
32
34
|
@state = :new
|
33
35
|
@streams = {}
|
36
|
+
@children = {}
|
34
37
|
|
35
38
|
@framer = framer
|
36
39
|
@local_stream_id = local_stream_id
|
@@ -50,6 +53,18 @@ module Protocol
|
|
50
53
|
0
|
51
54
|
end
|
52
55
|
|
56
|
+
def parent
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def [] id
|
61
|
+
if id.zero?
|
62
|
+
self
|
63
|
+
else
|
64
|
+
@streams[id]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
53
68
|
# The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting.
|
54
69
|
def maximum_frame_size
|
55
70
|
@remote_settings.maximum_frame_size
|
@@ -83,6 +98,7 @@ module Protocol
|
|
83
98
|
end
|
84
99
|
|
85
100
|
def active_streams
|
101
|
+
# TODO inefficient
|
86
102
|
@streams.each_value.select(&:active?)
|
87
103
|
end
|
88
104
|
|
@@ -100,7 +116,7 @@ module Protocol
|
|
100
116
|
end
|
101
117
|
|
102
118
|
def decode_headers(data)
|
103
|
-
HPACK::Decompressor.new(data, @decoder).decode
|
119
|
+
HPACK::Decompressor.new(data, @decoder, table_size_limit: @local_settings.header_table_size).decode
|
104
120
|
end
|
105
121
|
|
106
122
|
# Streams are identified with an unsigned 31-bit integer. Streams initiated by a client MUST use odd-numbered stream identifiers; those initiated by the server MUST use even-numbered stream identifiers. A stream identifier of zero (0x0) is used for connection control messages; the stream identifier of zero cannot be used to establish a new stream.
|
@@ -113,13 +129,34 @@ module Protocol
|
|
113
129
|
end
|
114
130
|
|
115
131
|
attr :streams
|
132
|
+
attr :children
|
133
|
+
|
134
|
+
def add_child(stream)
|
135
|
+
@children[stream.id] = stream
|
136
|
+
end
|
137
|
+
|
138
|
+
def remove_child(stream)
|
139
|
+
@children.delete(stream.id)
|
140
|
+
end
|
141
|
+
|
142
|
+
def exclusive_child(stream)
|
143
|
+
stream.children = @children
|
144
|
+
|
145
|
+
@children.each do |child|
|
146
|
+
child.dependent_id = stream.id
|
147
|
+
end
|
148
|
+
|
149
|
+
@children = {stream.id => stream}
|
150
|
+
|
151
|
+
stream.dependent_id = 0
|
152
|
+
end
|
116
153
|
|
117
154
|
# 6.8. GOAWAY
|
118
155
|
# There is an inherent race condition between an endpoint starting new streams and the remote sending a GOAWAY frame. To deal with this case, the GOAWAY contains the stream identifier of the last peer-initiated stream that was or might be processed on the sending endpoint in this connection. For instance, if the server sends a GOAWAY frame, the identified stream is the highest-numbered stream initiated by the client.
|
119
156
|
# Once sent, the sender will ignore frames sent on streams initiated by the receiver if the stream has an identifier higher than the included last stream identifier. Receivers of a GOAWAY frame MUST NOT open additional streams on the connection, although a new connection can be established for new streams.
|
120
157
|
def ignore_frame?(frame)
|
121
158
|
if self.closed?
|
122
|
-
puts "ignore_frame? #{frame.stream_id} -> #{valid_remote_stream_id?(frame.stream_id)} > #{@remote_stream_id}"
|
159
|
+
# puts "ignore_frame? #{frame.stream_id} -> #{valid_remote_stream_id?(frame.stream_id)} > #{@remote_stream_id}"
|
123
160
|
if valid_remote_stream_id?(frame.stream_id)
|
124
161
|
return frame.stream_id > @remote_stream_id
|
125
162
|
end
|
@@ -140,8 +177,6 @@ module Protocol
|
|
140
177
|
return frame
|
141
178
|
rescue GoawayError => error
|
142
179
|
# Go directly to jail. Do not pass go, do not collect $200.
|
143
|
-
self.close(error)
|
144
|
-
|
145
180
|
raise
|
146
181
|
rescue ProtocolError => error
|
147
182
|
send_goaway(error.code || PROTOCOL_ERROR, error.message)
|
@@ -150,10 +185,6 @@ module Protocol
|
|
150
185
|
rescue HPACK::CompressionError => error
|
151
186
|
send_goaway(COMPRESSION_ERROR, error.message)
|
152
187
|
|
153
|
-
raise
|
154
|
-
rescue
|
155
|
-
send_goaway(PROTOCOL_ERROR, $!.message)
|
156
|
-
|
157
188
|
raise
|
158
189
|
end
|
159
190
|
|
@@ -179,7 +210,7 @@ module Protocol
|
|
179
210
|
frame.pack @remote_stream_id, error_code, message
|
180
211
|
|
181
212
|
write_frame(frame)
|
182
|
-
|
213
|
+
ensure
|
183
214
|
self.close!
|
184
215
|
end
|
185
216
|
|
@@ -217,6 +248,8 @@ module Protocol
|
|
217
248
|
@streams.each_value do |stream|
|
218
249
|
stream.local_window.capacity = capacity
|
219
250
|
end
|
251
|
+
|
252
|
+
@decoder.table_size = @local_settings.header_table_size
|
220
253
|
end
|
221
254
|
|
222
255
|
def update_remote_settings(changes)
|
@@ -225,6 +258,8 @@ module Protocol
|
|
225
258
|
@streams.each_value do |stream|
|
226
259
|
stream.remote_window.capacity = capacity
|
227
260
|
end
|
261
|
+
|
262
|
+
@encoder.table_size = @remote_settings.header_table_size
|
228
263
|
end
|
229
264
|
|
230
265
|
# In addition to changing the flow-control window for streams that are not yet active, a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value.
|
@@ -304,15 +339,9 @@ module Protocol
|
|
304
339
|
# On the server side, we accept requests.
|
305
340
|
def accept_stream(stream_id, &block)
|
306
341
|
unless valid_remote_stream_id?(stream_id)
|
307
|
-
raise ProtocolError, "Invalid
|
342
|
+
raise ProtocolError, "Invalid stream id: #{stream_id}"
|
308
343
|
end
|
309
344
|
|
310
|
-
if stream_id <= @remote_stream_id
|
311
|
-
raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
|
312
|
-
end
|
313
|
-
|
314
|
-
@remote_stream_id = stream_id
|
315
|
-
|
316
345
|
create_stream(stream_id, &block)
|
317
346
|
end
|
318
347
|
|
@@ -326,17 +355,11 @@ module Protocol
|
|
326
355
|
# Create a stream, defaults to an outgoing stream.
|
327
356
|
# On the client side, we create requests.
|
328
357
|
# @return [Stream] the created stream.
|
329
|
-
def create_stream(
|
358
|
+
def create_stream(id = next_stream_id, &block)
|
330
359
|
if block_given?
|
331
|
-
yield(
|
360
|
+
return yield(self, id)
|
332
361
|
else
|
333
|
-
return Stream.
|
334
|
-
end
|
335
|
-
|
336
|
-
if stream = @streams[stream_id]
|
337
|
-
return stream
|
338
|
-
else
|
339
|
-
raise ProtocolError, "Stream creation failed!"
|
362
|
+
return Stream.create(self, id)
|
340
363
|
end
|
341
364
|
end
|
342
365
|
|
@@ -346,15 +369,23 @@ module Protocol
|
|
346
369
|
|
347
370
|
# On the server side, starts a new request.
|
348
371
|
def receive_headers(frame)
|
349
|
-
|
372
|
+
stream_id = frame.stream_id
|
373
|
+
|
374
|
+
if stream_id.zero?
|
350
375
|
raise ProtocolError, "Cannot receive headers for stream 0!"
|
351
376
|
end
|
352
377
|
|
353
|
-
if stream = @streams[
|
378
|
+
if stream = @streams[stream_id]
|
354
379
|
stream.receive_headers(frame)
|
355
380
|
else
|
381
|
+
if stream_id <= @remote_stream_id
|
382
|
+
raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
|
383
|
+
end
|
384
|
+
|
356
385
|
if self.active_streams.count < self.maximum_concurrent_streams
|
357
|
-
stream = accept_stream(
|
386
|
+
stream = accept_stream(stream_id)
|
387
|
+
@remote_stream_id = stream_id
|
388
|
+
|
358
389
|
stream.receive_headers(frame)
|
359
390
|
else
|
360
391
|
raise ProtocolError, "Exceeded maximum concurrent streams"
|
@@ -367,6 +398,7 @@ module Protocol
|
|
367
398
|
if stream = @streams[frame.stream_id]
|
368
399
|
stream.receive_priority(frame)
|
369
400
|
else
|
401
|
+
# Stream doesn't exist yet.
|
370
402
|
stream = accept_stream(frame.stream_id)
|
371
403
|
stream.receive_priority(frame)
|
372
404
|
end
|
@@ -387,6 +419,8 @@ module Protocol
|
|
387
419
|
def receive_window_update(frame)
|
388
420
|
if frame.connection?
|
389
421
|
super
|
422
|
+
|
423
|
+
self.consume_window
|
390
424
|
elsif stream = @streams[frame.stream_id]
|
391
425
|
begin
|
392
426
|
stream.receive_window_update(frame)
|
@@ -399,13 +433,6 @@ module Protocol
|
|
399
433
|
end
|
400
434
|
end
|
401
435
|
|
402
|
-
def window_updated
|
403
|
-
# This is very inefficient, but workable.
|
404
|
-
@streams.each_value do |stream|
|
405
|
-
stream.window_updated unless stream.closed?
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
436
|
def receive_continuation(frame)
|
410
437
|
raise ProtocolError, "Received unexpected continuation: #{frame.class}"
|
411
438
|
end
|
data/lib/protocol/http2/error.rb
CHANGED
@@ -86,7 +86,13 @@ module Protocol
|
|
86
86
|
attr :code
|
87
87
|
end
|
88
88
|
|
89
|
-
class
|
89
|
+
class HeaderError < ProtocolError
|
90
|
+
end
|
91
|
+
|
92
|
+
class StreamError < ProtocolError
|
93
|
+
end
|
94
|
+
|
95
|
+
class StreamClosed < StreamError
|
90
96
|
def initialize(message)
|
91
97
|
super message, STREAM_CLOSED
|
92
98
|
end
|
@@ -23,9 +23,13 @@ require_relative 'window_update_frame'
|
|
23
23
|
module Protocol
|
24
24
|
module HTTP2
|
25
25
|
module FlowControl
|
26
|
-
def
|
27
|
-
|
28
|
-
|
26
|
+
def available_size
|
27
|
+
@remote_window.available
|
28
|
+
end
|
29
|
+
|
30
|
+
# This could be negative if the window has been overused due to a change in initial window size.
|
31
|
+
def available_frame_size(maximum_frame_size = self.maximum_frame_size)
|
32
|
+
available_size = self.available_size
|
29
33
|
|
30
34
|
# puts "available_size=#{available_size} maximum_frame_size=#{maximum_frame_size}"
|
31
35
|
|
@@ -71,10 +75,9 @@ module Protocol
|
|
71
75
|
end
|
72
76
|
|
73
77
|
def receive_window_update(frame)
|
74
|
-
was_full = @remote_window.full?
|
75
|
-
|
76
78
|
amount = frame.unpack
|
77
|
-
|
79
|
+
|
80
|
+
# puts "expanding remote_window=#{@remote_window} by #{amount}"
|
78
81
|
|
79
82
|
if amount != 0
|
80
83
|
@remote_window.expand(amount)
|
@@ -82,10 +85,39 @@ module Protocol
|
|
82
85
|
raise ProtocolError, "Invalid window size increment: #{amount}!"
|
83
86
|
end
|
84
87
|
|
85
|
-
|
88
|
+
# puts "expanded remote_window=#{@remote_window} by #{amount}"
|
86
89
|
end
|
87
90
|
|
88
|
-
|
91
|
+
# The window has been expanded by the given amount.
|
92
|
+
# @param size [Integer] the maximum amount of data to send.
|
93
|
+
# @return [Boolean] whether the window update was used or not.
|
94
|
+
def window_updated(size = self.available_size)
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
# Traverse active streams in order of priority and allow them to consume the available flow-control window.
|
99
|
+
# @param amount [Integer] the amount of data to write. Defaults to the current window capacity.
|
100
|
+
def consume_window(size = self.available_size)
|
101
|
+
# Don't consume more than the available window size:
|
102
|
+
size = [self.available_size, size].min
|
103
|
+
# puts "consume_window(#{size}) local_window=#{@local_window} remote_window=#{@remote_window}"
|
104
|
+
|
105
|
+
# Return if there is no window to consume:
|
106
|
+
return unless size > 0
|
107
|
+
|
108
|
+
# Allow the current flow-controlled instance to use up the window:
|
109
|
+
if !self.window_updated(size) and children = self.children
|
110
|
+
children = children.values.sort_by(&:weight)
|
111
|
+
|
112
|
+
# This must always be at least >= `children.count`, since stream weight can't be 0.
|
113
|
+
total = children.sum(&:weight)
|
114
|
+
|
115
|
+
children.each do |child|
|
116
|
+
# Compute the proportional allocation:
|
117
|
+
allocated = (child.weight * size) / total
|
118
|
+
child.consume_window(allocated)
|
119
|
+
end
|
120
|
+
end
|
89
121
|
end
|
90
122
|
end
|
91
123
|
end
|
data/lib/protocol/http2/frame.rb
CHANGED
@@ -52,6 +52,8 @@ module Protocol
|
|
52
52
|
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
|
53
53
|
|
54
54
|
class Framer
|
55
|
+
# DEBUG = !!ENV['FRAMER_DEBUG']
|
56
|
+
|
55
57
|
def initialize(stream, frames = FRAMES)
|
56
58
|
@stream = stream
|
57
59
|
@frames = frames
|
@@ -79,6 +81,8 @@ module Protocol
|
|
79
81
|
return string
|
80
82
|
end
|
81
83
|
|
84
|
+
# @return [Frame] the frame that has been read from the underlying IO.
|
85
|
+
# @raise if the underlying IO fails for some reason.
|
82
86
|
def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
|
83
87
|
# Read the header:
|
84
88
|
length, type, flags, stream_id = read_header
|
@@ -86,18 +90,18 @@ module Protocol
|
|
86
90
|
|
87
91
|
# Allocate the frame:
|
88
92
|
klass = @frames[type] || Frame
|
89
|
-
# puts "read_frame #{klass} id=#{stream_id} length=#{length} flags=#{flags}"
|
90
|
-
|
91
93
|
frame = klass.new(stream_id, flags, type, length)
|
92
94
|
|
93
95
|
# Read the payload:
|
94
96
|
frame.read(@stream, maximum_frame_size)
|
95
97
|
|
98
|
+
# DEBUG and puts "read_frame: #{frame.inspect}"
|
99
|
+
|
96
100
|
return frame
|
97
101
|
end
|
98
102
|
|
99
103
|
def write_frame(frame)
|
100
|
-
# puts "
|
104
|
+
# DEBUG and puts "write_frame: #{frame.inspect}"
|
101
105
|
frame.write(@stream)
|
102
106
|
|
103
107
|
@stream.flush
|
@@ -22,6 +22,8 @@ require_relative 'frame'
|
|
22
22
|
|
23
23
|
module Protocol
|
24
24
|
module HTTP2
|
25
|
+
VALID_WEIGHT = (1..256)
|
26
|
+
|
25
27
|
# Stream Dependency: A 31-bit stream identifier for the stream that
|
26
28
|
# this stream depends on (see Section 5.3). This field is only
|
27
29
|
# present if the PRIORITY flag is set.
|
@@ -29,10 +31,16 @@ module Protocol
|
|
29
31
|
FORMAT = "NC".freeze
|
30
32
|
EXCLUSIVE = 1 << 31
|
31
33
|
|
34
|
+
# All streams are initially assigned a non-exclusive dependency on stream 0x0. Pushed streams (Section 8.2) initially depend on their associated stream. In both cases, streams are assigned a default weight of 16.
|
35
|
+
def self.default(stream_dependency = 0, weight = 16)
|
36
|
+
self.new(false, stream_dependency, weight)
|
37
|
+
end
|
38
|
+
|
32
39
|
def self.unpack(data)
|
33
40
|
stream_dependency, weight = data.unpack(FORMAT)
|
34
41
|
|
35
|
-
|
42
|
+
# Weight: An unsigned 8-bit integer representing a priority weight for the stream (see Section 5.3). Add one to the value to obtain a weight between 1 and 256. This field is only present if the PRIORITY flag is set.
|
43
|
+
return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight + 1)
|
36
44
|
end
|
37
45
|
|
38
46
|
def pack
|
@@ -40,7 +48,15 @@ module Protocol
|
|
40
48
|
stream_dependency = self.stream_dependency | EXCLUSIVE
|
41
49
|
end
|
42
50
|
|
43
|
-
return [stream_dependency, self.weight].pack(FORMAT)
|
51
|
+
return [stream_dependency, self.weight - 1].pack(FORMAT)
|
52
|
+
end
|
53
|
+
|
54
|
+
def weight= value
|
55
|
+
if VALID_WEIGHT.include?(value)
|
56
|
+
super
|
57
|
+
else
|
58
|
+
raise ArgumentError, "Weight #{value} must be between 1-256!"
|
59
|
+
end
|
44
60
|
end
|
45
61
|
end
|
46
62
|
|
@@ -38,7 +38,7 @@ module Protocol
|
|
38
38
|
send_settings(settings)
|
39
39
|
|
40
40
|
read_frame do |frame|
|
41
|
-
raise ProtocolError, "First frame must be SettingsFrame, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
41
|
+
raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}" unless frame.is_a? SettingsFrame
|
42
42
|
end
|
43
43
|
else
|
44
44
|
raise ProtocolError, "Cannot send connection preface in state #{@state}"
|
@@ -231,11 +231,16 @@ module Protocol
|
|
231
231
|
end
|
232
232
|
|
233
233
|
def unpack
|
234
|
-
super
|
234
|
+
if buffer = super
|
235
|
+
# TODO String#each_slice, or #each_unpack would be nice.
|
236
|
+
buffer.scan(/....../m).map{|s| s.unpack(FORMAT)}
|
237
|
+
else
|
238
|
+
[]
|
239
|
+
end
|
235
240
|
end
|
236
241
|
|
237
242
|
def pack(settings = [])
|
238
|
-
super
|
243
|
+
super(settings.map{|s| s.pack(FORMAT)}.join)
|
239
244
|
end
|
240
245
|
|
241
246
|
def apply(connection)
|
@@ -74,44 +74,136 @@ module Protocol
|
|
74
74
|
class Stream
|
75
75
|
include FlowControl
|
76
76
|
|
77
|
-
def
|
77
|
+
def self.create(connection, id = connection.next_stream_id)
|
78
|
+
local_window = Window.new(connection.local_settings.initial_window_size)
|
79
|
+
remote_window = Window.new(connection.remote_settings.initial_window_size)
|
80
|
+
|
81
|
+
stream = self.new(connection, id, local_window, remote_window)
|
82
|
+
|
83
|
+
# Create new stream:
|
84
|
+
connection.streams[id] = stream
|
85
|
+
stream.parent.add_child(stream)
|
86
|
+
|
87
|
+
return stream
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.replace(stream)
|
91
|
+
connection = stream.connection
|
92
|
+
stream.parent.remove_child(stream)
|
93
|
+
|
94
|
+
stream = self.new(
|
95
|
+
connection, stream.id, stream.local_window, stream.remote_window,
|
96
|
+
stream.state, stream.dependent_id, stream.weight, stream.children
|
97
|
+
)
|
98
|
+
|
99
|
+
# Replace existing stream:
|
100
|
+
connection.streams[stream.id] = stream
|
101
|
+
stream.parent.add_child(stream)
|
102
|
+
|
103
|
+
return stream
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.accept(connection, id)
|
107
|
+
if stream = connection.streams[id]
|
108
|
+
self.replace(stream)
|
109
|
+
else
|
110
|
+
self.create(connection, id)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def initialize(connection, id, local_window, remote_window, state = :idle, dependent_id = 0, weight = 16, children = nil)
|
78
115
|
@connection = connection
|
79
116
|
@id = id
|
80
117
|
|
81
|
-
@
|
118
|
+
@local_window = local_window
|
119
|
+
@remote_window = remote_window
|
82
120
|
|
83
|
-
@
|
84
|
-
@local_window = Window.new(connection.local_settings.initial_window_size)
|
85
|
-
@remote_window = Window.new(connection.remote_settings.initial_window_size)
|
121
|
+
@state = state
|
86
122
|
|
87
|
-
|
88
|
-
@
|
123
|
+
# Stream priority:
|
124
|
+
@dependent_id = dependent_id
|
125
|
+
@weight = weight
|
89
126
|
|
90
|
-
|
127
|
+
# A cache of streams that have child.dependent_id = self.id
|
128
|
+
@children = children
|
91
129
|
end
|
92
130
|
|
93
|
-
#
|
94
|
-
|
95
|
-
|
131
|
+
# Cache of dependent children.
|
132
|
+
attr_accessor :children
|
133
|
+
|
134
|
+
# The connection this stream belongs to.
|
135
|
+
attr :connection
|
96
136
|
|
97
137
|
# Stream ID (odd for client initiated streams, even otherwise).
|
98
138
|
attr :id
|
99
139
|
|
100
|
-
# Stream state
|
101
|
-
|
140
|
+
# Stream state, e.g. `idle`, `closed`.
|
141
|
+
attr_accessor :state
|
142
|
+
|
143
|
+
# The stream id that this stream depends on, according to the priority.
|
144
|
+
attr_accessor :dependent_id
|
102
145
|
|
103
|
-
|
104
|
-
|
146
|
+
# The weight of the stream relative to other siblings.
|
147
|
+
attr_accessor :weight
|
105
148
|
|
106
149
|
attr :local_window
|
107
150
|
attr :remote_window
|
108
151
|
|
152
|
+
def add_child(stream)
|
153
|
+
@children ||= {}
|
154
|
+
@children[stream.id] = stream
|
155
|
+
end
|
156
|
+
|
157
|
+
def remove_child(stream)
|
158
|
+
@children.delete(stream.id)
|
159
|
+
end
|
160
|
+
|
161
|
+
def exclusive_child(stream)
|
162
|
+
stream.children = @children
|
163
|
+
|
164
|
+
@children.each do |child|
|
165
|
+
child.dependent_id = stream.id
|
166
|
+
end
|
167
|
+
|
168
|
+
@children = {stream.id => stream}
|
169
|
+
|
170
|
+
stream.dependent_id = @id
|
171
|
+
end
|
172
|
+
|
173
|
+
def parent(id = @dependent_id)
|
174
|
+
@connection[id] || @connection.accept_stream(id)
|
175
|
+
end
|
176
|
+
|
177
|
+
def parent= stream
|
178
|
+
self.parent&.remove_child(self)
|
179
|
+
|
180
|
+
@dependent_id = stream.id
|
181
|
+
|
182
|
+
stream.add_child(self)
|
183
|
+
end
|
184
|
+
|
109
185
|
def priority= priority
|
110
|
-
|
186
|
+
dependent_id = priority.stream_dependency
|
187
|
+
|
188
|
+
if dependent_id == @id
|
111
189
|
raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
|
112
190
|
end
|
113
191
|
|
114
|
-
|
192
|
+
if priority.exclusive
|
193
|
+
self.parent&.remove_child(self)
|
194
|
+
|
195
|
+
self.parent(dependent_id).exclusive_child(self)
|
196
|
+
elsif dependent_id != @dependent_id
|
197
|
+
self.parent&.remove_child(self)
|
198
|
+
|
199
|
+
@dependent_id = dependent_id
|
200
|
+
|
201
|
+
self.parent.add_child(self)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# The stream is being closed because the connection is being closed.
|
206
|
+
def close(error = nil)
|
115
207
|
end
|
116
208
|
|
117
209
|
def maximum_frame_size
|
@@ -134,19 +226,6 @@ module Protocol
|
|
134
226
|
@state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote
|
135
227
|
end
|
136
228
|
|
137
|
-
def send_failure(status, reason)
|
138
|
-
if send_headers?
|
139
|
-
send_headers(nil, [
|
140
|
-
[':status', status.to_s],
|
141
|
-
['reason', reason]
|
142
|
-
], END_STREAM)
|
143
|
-
else
|
144
|
-
send_reset_stream(PROTOCOL_ERROR)
|
145
|
-
end
|
146
|
-
|
147
|
-
return nil
|
148
|
-
end
|
149
|
-
|
150
229
|
private def write_headers(priority, headers, flags = 0)
|
151
230
|
data = @connection.encode_headers(headers)
|
152
231
|
|
@@ -201,7 +280,6 @@ module Protocol
|
|
201
280
|
|
202
281
|
# This might fail if the data payload was too big:
|
203
282
|
consume_remote_window(frame)
|
204
|
-
|
205
283
|
write_frame(frame)
|
206
284
|
|
207
285
|
return frame
|
@@ -226,10 +304,15 @@ module Protocol
|
|
226
304
|
end
|
227
305
|
|
228
306
|
# Transition the stream into the closed state.
|
229
|
-
|
307
|
+
# @param error_code [Integer] the error code if the stream was closed due to a stream reset.
|
308
|
+
def close!(error_code = nil)
|
230
309
|
@state = :closed
|
231
310
|
|
232
|
-
|
311
|
+
if error_code
|
312
|
+
error = StreamError.new("Stream closed!", error_code)
|
313
|
+
end
|
314
|
+
|
315
|
+
self.close(error)
|
233
316
|
end
|
234
317
|
|
235
318
|
def send_reset_stream(error_code = 0)
|
@@ -264,28 +347,33 @@ module Protocol
|
|
264
347
|
@state = :open
|
265
348
|
end
|
266
349
|
|
267
|
-
|
350
|
+
return process_headers(frame)
|
268
351
|
elsif @state == :reserved_remote
|
269
352
|
@state = :half_closed_local
|
270
353
|
|
271
|
-
|
354
|
+
return process_headers(frame)
|
272
355
|
elsif @state == :open
|
273
356
|
if frame.end_stream?
|
274
357
|
@state = :half_closed_remote
|
275
358
|
end
|
276
359
|
|
277
|
-
|
360
|
+
return process_headers(frame)
|
278
361
|
elsif @state == :half_closed_local
|
279
362
|
if frame.end_stream?
|
280
363
|
close!
|
281
364
|
end
|
282
365
|
|
283
|
-
|
366
|
+
return process_headers(frame)
|
284
367
|
else
|
285
368
|
raise ProtocolError, "Cannot receive headers in state: #{@state}"
|
286
369
|
end
|
287
370
|
end
|
288
371
|
|
372
|
+
# @return [String] the data that was received.
|
373
|
+
def process_data(frame)
|
374
|
+
frame.unpack
|
375
|
+
end
|
376
|
+
|
289
377
|
# DATA frames are subject to flow control and can only be sent when a stream is in the "open" or "half-closed (remote)" state. The entire DATA frame payload is included in flow control, including the Pad Length and Padding fields if present. If a DATA frame is received whose stream is not in "open" or "half-closed (local)" state, the recipient MUST respond with a stream error of type STREAM_CLOSED.
|
290
378
|
def receive_data(frame)
|
291
379
|
if @state == :open
|
@@ -295,15 +383,15 @@ module Protocol
|
|
295
383
|
@state = :half_closed_remote
|
296
384
|
end
|
297
385
|
|
298
|
-
|
386
|
+
process_data(frame)
|
299
387
|
elsif @state == :half_closed_local
|
300
388
|
consume_local_window(frame)
|
301
389
|
|
390
|
+
process_data(frame)
|
391
|
+
|
302
392
|
if frame.end_stream?
|
303
393
|
close!
|
304
394
|
end
|
305
|
-
|
306
|
-
@data = frame.unpack
|
307
395
|
else
|
308
396
|
raise ProtocolError, "Cannot receive data in state: #{@state}"
|
309
397
|
end
|
@@ -315,9 +403,11 @@ module Protocol
|
|
315
403
|
|
316
404
|
def receive_reset_stream(frame)
|
317
405
|
if @state != :idle and @state != :closed
|
318
|
-
|
406
|
+
error_code = frame.unpack
|
407
|
+
|
408
|
+
close!(error_code)
|
319
409
|
|
320
|
-
return
|
410
|
+
return error_code
|
321
411
|
else
|
322
412
|
raise ProtocolError, "Cannot reset stream in state: #{@state}"
|
323
413
|
end
|
@@ -383,6 +473,7 @@ module Protocol
|
|
383
473
|
headers = @connection.decode_headers(data)
|
384
474
|
|
385
475
|
stream = self.accept_push_promise_stream(promised_stream_id, headers)
|
476
|
+
stream.parent = self
|
386
477
|
stream.reserved_remote!
|
387
478
|
|
388
479
|
return stream, headers
|
@@ -49,6 +49,7 @@ module Protocol
|
|
49
49
|
def capacity= value
|
50
50
|
difference = value - @capacity
|
51
51
|
@available += difference
|
52
|
+
@capacity = value
|
52
53
|
end
|
53
54
|
|
54
55
|
def consume(amount)
|
@@ -63,6 +64,7 @@ module Protocol
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def expand(amount)
|
67
|
+
# puts "expand(#{amount}) @available=#{@available}"
|
66
68
|
@available += amount
|
67
69
|
@used -= amount
|
68
70
|
|
data/protocol-http2.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "protocol-hpack", "~> 1.
|
21
|
+
spec.add_dependency "protocol-hpack", "~> 1.2"
|
22
22
|
spec.add_dependency "protocol-http", "~> 0.2"
|
23
23
|
|
24
24
|
spec.add_development_dependency "covered"
|
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.7.0
|
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-06-
|
11
|
+
date: 2019-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: protocol-hpack
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: protocol-http
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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.2
|
154
154
|
signing_key:
|
155
155
|
specification_version: 4
|
156
156
|
summary: A low level implementation of the HTTP/2 protocol.
|