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 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.