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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5f71a68091b631d1c6a776ae1730539cf753ee6ec24d27dc3b1d739dd971426
4
- data.tar.gz: ea202fecbfe30bfbe0b9facf5d507f09344a2a959822be6f22d2d082cf273477
3
+ metadata.gz: 49e091bc1a6d2b2bdee4176a13648d306f81042ebc6d25cf0a44a921d33d0950
4
+ data.tar.gz: 5ecd3a8454c2a130247c624abdd067d87b38e63c09e9ed88fe2214e52461ac6c
5
5
  SHA512:
6
- metadata.gz: 9d1a0a6e6a8461e985fcb9dc83e64c622743cb3d05e52d3b72f1b8a0f48d8190ac01417070807ada1d4ee0434dd7daa775d054ca89d54ba2010d6c153d9fad91
7
- data.tar.gz: a17e3d970e3a2539361f518aff2ccc5ec40ca77f9b639a0b6bd69aabab1d0ade8a2e1f7049c08f5a001acfa9eb2fca4c5f2e785475d09e0c21c8088cec050d97
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 remote stream id: #{stream_id}"
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(stream_id = next_stream_id, &block)
358
+ def create_stream(id = next_stream_id, &block)
330
359
  if block_given?
331
- yield(stream_id)
360
+ return yield(self, id)
332
361
  else
333
- return Stream.new(self, stream_id)
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
- if frame.stream_id == 0
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[frame.stream_id]
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(frame.stream_id)
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
@@ -110,6 +110,10 @@ module Protocol
110
110
  def apply(connection)
111
111
  connection.receive_continuation(self)
112
112
  end
113
+
114
+ def inspect
115
+ "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>"
116
+ end
113
117
  end
114
118
  end
115
119
  end
@@ -57,6 +57,10 @@ module Protocol
57
57
  def apply(connection)
58
58
  connection.receive_data(self)
59
59
  end
60
+
61
+ def inspect
62
+ "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>"
63
+ end
60
64
  end
61
65
  end
62
66
  end
@@ -86,7 +86,13 @@ module Protocol
86
86
  attr :code
87
87
  end
88
88
 
89
- class StreamClosed < ProtocolError
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 available_frame_size
27
- maximum_frame_size = self.maximum_frame_size
28
- available_size = @remote_window.available
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
- # puts "expand remote_window=#{@remote_window} by #{amount}"
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
- self.window_updated if was_full
88
+ # puts "expanded remote_window=#{@remote_window} by #{amount}"
86
89
  end
87
90
 
88
- def window_updated
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
@@ -200,6 +200,10 @@ module Protocol
200
200
  def apply(connection)
201
201
  connection.receive_frame(self)
202
202
  end
203
+
204
+ def inspect
205
+ "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{self.unpack}>"
206
+ end
203
207
  end
204
208
  end
205
209
  end
@@ -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 "framer: write_frame #{frame.inspect}"
104
+ # DEBUG and puts "write_frame: #{frame.inspect}"
101
105
  frame.write(@stream)
102
106
 
103
107
  @stream.flush
@@ -78,6 +78,10 @@ module Protocol
78
78
  def apply(connection)
79
79
  connection.receive_headers(self)
80
80
  end
81
+
82
+ def inspect
83
+ "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length}b>"
84
+ end
81
85
  end
82
86
  end
83
87
  end
@@ -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
- return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight)
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.scan(/....../).map{|s| s.unpack(FORMAT)}
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 settings.map{|s| s.pack(FORMAT)}.join
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 initialize(connection, id = connection.next_stream_id)
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
- @state = :idle
118
+ @local_window = local_window
119
+ @remote_window = remote_window
82
120
 
83
- @priority = nil
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
- @headers = nil
88
- @data = nil
123
+ # Stream priority:
124
+ @dependent_id = dependent_id
125
+ @weight = weight
89
126
 
90
- @connection.streams[@id] = self
127
+ # A cache of streams that have child.dependent_id = self.id
128
+ @children = children
91
129
  end
92
130
 
93
- # The stream is being closed because the connection is being closed.
94
- def close(error = nil)
95
- end
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 as defined by HTTP 2.0.
101
- attr :state
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
- attr :headers
104
- attr :data
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
- if priority.stream_dependency == @id
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
- @priority = priority
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
- def close!
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
- self.close
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
- @headers = process_headers(frame)
350
+ return process_headers(frame)
268
351
  elsif @state == :reserved_remote
269
352
  @state = :half_closed_local
270
353
 
271
- @headers = process_headers(frame)
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
- @headers = process_headers(frame)
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
- @headers = process_headers(frame)
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
- @data = frame.unpack
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
- close!
406
+ error_code = frame.unpack
407
+
408
+ close!(error_code)
319
409
 
320
- return frame.unpack
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
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Protocol
22
22
  module HTTP2
23
- VERSION = "0.6.0"
23
+ VERSION = "0.7.0"
24
24
  end
25
25
  end
@@ -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
 
@@ -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.0"
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.6.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-13 00:00:00.000000000 Z
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.0'
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.0'
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.3
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.