protocol-http2 0.6.0 → 0.7.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/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.
|