protocol-http2 0.10.4 → 0.11.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: c7ca4d904325a7bfe60c54aecf87ea7eab1d1addbfe0cdf38f1cead97e37be8c
4
- data.tar.gz: 87eb242fecbeda4519aa85274d6d3199fd71965531d9ecf29d3e67dfc6081548
3
+ metadata.gz: 014c5404c1c741f09ed296d51da864e5e50ed7ecf336da2d2360fe8bed69fff8
4
+ data.tar.gz: 8f7e6fcbedfe36e3eaea28f8e1fabd7488c82f0465dad54927a1a9216a191465
5
5
  SHA512:
6
- metadata.gz: f9991a66cd567b5f524675c49cc10aa01a002cef790c2de3ff90b8f63153deb008750e6b90d93497c97ef3aab18c13b7b243a2c72b01785399bd5fcc28ce5694
7
- data.tar.gz: 1eb905670918fdff2eb57964fe7f229d8094a848e9e17b41c407a05ca74936e044c27e319170298f0e05c2247babdd785e68a52158a6dced281508114677e67d
6
+ metadata.gz: 2d9f8ff2e36fdabb2aa72afd3903d410d0d0ebc660a78a865de6610768629d682f41e90ea39ece505159a97f50a4b87d6cb061c99af2759e0a4901b8a3275f9f
7
+ data.tar.gz: c66a7bf1538d34011cb2b40aa8ea1502079c88c8709cd6969216da8b355f26b301fd6e58e65b8e296333a2b4fc5f68a552f02b14fe15d8bad4c40fc378e0b077
@@ -8,6 +8,7 @@ matrix:
8
8
  - rvm: 2.4
9
9
  - rvm: 2.5
10
10
  - rvm: 2.6
11
+ - rvm: 2.7
11
12
  - rvm: 2.6
12
13
  env: COVERAGE=PartialSummary,Coveralls
13
14
  - rvm: truffleruby
@@ -19,7 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'framer'
22
- require_relative 'flow_control'
22
+ require_relative 'dependency'
23
23
 
24
24
  require 'protocol/hpack'
25
25
 
@@ -32,10 +32,13 @@ module Protocol
32
32
  super()
33
33
 
34
34
  @state = :new
35
+
36
+ # Hash(Integer, Stream)
35
37
  @streams = {}
36
- @children = {}
37
38
 
38
- @active = 0
39
+ # Hash(Integer, Dependency)
40
+ @dependency = Dependency.new(self, 0)
41
+ @dependencies = {0 => @dependency}
39
42
 
40
43
  @framer = framer
41
44
  @local_stream_id = local_stream_id
@@ -59,6 +62,10 @@ module Protocol
59
62
  nil
60
63
  end
61
64
 
65
+ def children
66
+ @dependency.streams
67
+ end
68
+
62
69
  def [] id
63
70
  if id.zero?
64
71
  self
@@ -99,24 +106,18 @@ module Protocol
99
106
  @state == :closed
100
107
  end
101
108
 
102
- # The stream has become active (i.e. not idle or closed).
103
- def activate(stream)
104
- @active += 1
105
- end
106
-
107
- # The stream is no longer active (i.e. has become closed).
108
- def deactivate(stream)
109
- @active -= 1
110
- end
111
-
112
- # The number of active streams.
113
- def active_streams
114
- @active
109
+ def delete(id)
110
+ @streams.delete(id)
111
+
112
+ if dependency = @dependencies[id]
113
+ dependency.delete! if dependency.irrelevant?
114
+ end
115
115
  end
116
116
 
117
117
  # Close the underlying framer and all streams.
118
118
  def close(error = nil)
119
119
  @framer.close
120
+ # @framer = nil
120
121
 
121
122
  @streams.each_value{|stream| stream.close(error)}
122
123
  end
@@ -139,26 +140,19 @@ module Protocol
139
140
  end
140
141
 
141
142
  attr :streams
142
- attr :children
143
-
144
- def add_child(stream)
145
- @children[stream.id] = stream
146
- end
143
+ attr :dependencies
147
144
 
148
- def remove_child(stream)
149
- @children.delete(stream.id)
150
- end
151
-
152
- def exclusive_child(stream)
153
- stream.children = @children
154
-
155
- @children.each_value do |child|
156
- child.dependent_id = stream.id
145
+ # Fetch (or create) the flow control windows for the specified stream id.
146
+ # @param id [Integer] the stream id.
147
+ def dependency_for(id)
148
+ @dependencies.fetch(id) do
149
+ dependency = Dependency.new(self, id)
150
+
151
+ # TODO this might be irrelevant, if initially processing priority frame.
152
+ @dependency.add_child(dependency)
153
+
154
+ @dependencies[id] = dependency
157
155
  end
158
-
159
- @children = {stream.id => stream}
160
-
161
- stream.dependent_id = 0
162
156
  end
163
157
 
164
158
  # 6.8. GOAWAY
@@ -398,7 +392,7 @@ module Protocol
398
392
  raise ProtocolError, "Invalid stream id: #{stream_id} <= #{@remote_stream_id}!"
399
393
  end
400
394
 
401
- if self.active_streams.size < self.maximum_concurrent_streams
395
+ if @streams.size < self.maximum_concurrent_streams
402
396
  stream = accept_stream(stream_id)
403
397
  @remote_stream_id = stream_id
404
398
 
@@ -418,13 +412,8 @@ module Protocol
418
412
 
419
413
  # Sets the priority for an incoming stream.
420
414
  def receive_priority(frame)
421
- if stream = @streams[frame.stream_id]
422
- stream.receive_priority(frame)
423
- else
424
- # Stream doesn't exist yet.
425
- stream = accept_stream(frame.stream_id)
426
- stream.receive_priority(frame)
427
- end
415
+ dependency = dependency_for(frame.stream_id)
416
+ dependency.receive_priority(frame)
428
417
  end
429
418
 
430
419
  def receive_push_promise(frame)
@@ -450,7 +439,7 @@ module Protocol
450
439
  rescue ProtocolError => error
451
440
  stream.send_reset_stream(error.code)
452
441
  end
453
- else
442
+ elsif frame.stream_id > @remote_stream_id
454
443
  # Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be treated as a connection error of type PROTOCOL_ERROR.
455
444
  raise ProtocolError, "Cannot update window of idle stream #{frame.stream_id}"
456
445
  end
@@ -0,0 +1,151 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'flow_control'
22
+
23
+ module Protocol
24
+ module HTTP2
25
+ DEFAULT_WEIGHT = 16
26
+
27
+ class Dependency
28
+ def initialize(connection, id, dependent_id = 0, weight = DEFAULT_WEIGHT, children = nil)
29
+ @connection = connection
30
+ @id = id
31
+
32
+ # Stream priority:
33
+ @dependent_id = dependent_id
34
+ @weight = weight
35
+
36
+ # A cache of dependencies that have child.dependent_id = self.id
37
+ @children = children
38
+ end
39
+
40
+ def irrelevant?
41
+ (@weight == DEFAULT_WEIGHT) && (@children.nil? || @children.empty?)
42
+ end
43
+
44
+ def delete!
45
+ @connection.dependencies.delete(@id)
46
+ self.parent&.remove_child(self)
47
+ end
48
+
49
+ # Cache of dependent children.
50
+ attr_accessor :children
51
+
52
+ # The connection this stream belongs to.
53
+ attr :connection
54
+
55
+ # Stream ID (odd for client initiated streams, even otherwise).
56
+ attr :id
57
+
58
+ # The stream id that this stream depends on, according to the priority.
59
+ attr_accessor :dependent_id
60
+
61
+ # The weight of the stream relative to other siblings.
62
+ attr_accessor :weight
63
+
64
+ def stream
65
+ @connection.streams[@id]
66
+ end
67
+
68
+ def streams
69
+ if @children
70
+ # TODO this O(N) operation affects performance.
71
+ # It would be better to maintain a sorted list of children streams.
72
+ @children.map{|id, dependency| dependency.stream}.compact
73
+ end
74
+ end
75
+
76
+ def add_child(dependency)
77
+ @children ||= {}
78
+ @children[dependency.id] = dependency
79
+ end
80
+
81
+ def remove_child(dependency)
82
+ @children&.delete(dependency.id)
83
+ end
84
+
85
+ def exclusive_child(parent)
86
+ parent.children = @children
87
+
88
+ @children.each_value do |child|
89
+ child.dependent_id = parent.id
90
+ end
91
+
92
+ @children = {parent.id => parent}
93
+
94
+ parent.dependent_id = @id
95
+ end
96
+
97
+ def parent(id = @dependent_id)
98
+ @connection.dependency_for(id)
99
+ end
100
+
101
+ def parent= dependency
102
+ self.parent&.remove_child(self)
103
+
104
+ @dependent_id = dependency.id
105
+
106
+ dependency.add_child(self)
107
+ end
108
+
109
+ def process_priority priority
110
+ dependent_id = priority.stream_dependency
111
+
112
+ if dependent_id == @id
113
+ raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
114
+ end
115
+
116
+ @weight = priority.weight
117
+
118
+ if priority.exclusive
119
+ self.parent&.remove_child(self)
120
+
121
+ self.parent(dependent_id).exclusive_child(self)
122
+ elsif dependent_id != @dependent_id
123
+ self.parent&.remove_child(self)
124
+
125
+ @dependent_id = dependent_id
126
+
127
+ self.parent.add_child(self)
128
+ end
129
+ end
130
+
131
+ # Change the priority of the stream both locally and remotely.
132
+ def priority= priority
133
+ send_priority(priority)
134
+ process_priority(priority)
135
+ end
136
+
137
+ # The current local priority of the stream.
138
+ def priority(exclusive = false)
139
+ Priority.new(exclusive, @dependent_id, @weight)
140
+ end
141
+
142
+ def send_priority(priority)
143
+ @connection.send_priority(@id, priority)
144
+ end
145
+
146
+ def receive_priority(frame)
147
+ self.process_priority(frame.unpack)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -115,7 +115,7 @@ module Protocol
115
115
 
116
116
  # Allow the current flow-controlled instance to use up the window:
117
117
  if !self.window_updated(size) and children = self.children
118
- children = children.values.sort_by(&:weight)
118
+ children = children.sort_by(&:weight)
119
119
 
120
120
  # This must always be at least >= `children.size`, since stream weight can't be 0.
121
121
  total = children.sum(&:weight)
@@ -19,7 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'connection'
22
- require_relative 'flow_control'
22
+ require_relative 'dependency'
23
23
 
24
24
  module Protocol
25
25
  module HTTP2
@@ -74,142 +74,63 @@ module Protocol
74
74
  class Stream
75
75
  include FlowControl
76
76
 
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)
77
+ def self.create(connection, id)
78
+ stream = self.new(connection, id)
80
79
 
81
- stream = self.new(connection, id, local_window, remote_window)
82
-
83
- # Create new stream:
84
80
  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
81
 
103
82
  return stream
104
83
  end
105
84
 
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)
85
+ def initialize(connection, id, state = :idle)
115
86
  @connection = connection
116
87
  @id = id
117
88
 
118
- @local_window = local_window
119
- @remote_window = remote_window
120
-
121
89
  @state = state
122
90
 
123
- if active?
124
- connnection.active(self)
125
- end
126
-
127
- # Stream priority:
128
- @dependent_id = dependent_id
129
- @weight = weight
91
+ @local_window = Window.new(@connection.local_settings.initial_window_size)
92
+ @remote_window = Window.new(@connection.remote_settings.initial_window_size)
130
93
 
131
- # A cache of streams that have child.dependent_id = self.id
132
- @children = children
94
+ @dependency = @connection.dependency_for(@id)
133
95
  end
134
96
 
135
- # Cache of dependent children.
136
- attr_accessor :children
137
-
138
97
  # The connection this stream belongs to.
139
98
  attr :connection
140
99
 
141
100
  # Stream ID (odd for client initiated streams, even otherwise).
142
101
  attr :id
143
-
102
+
144
103
  # Stream state, e.g. `idle`, `closed`.
145
104
  attr_accessor :state
146
105
 
147
- # The stream id that this stream depends on, according to the priority.
148
- attr_accessor :dependent_id
149
-
150
- # The weight of the stream relative to other siblings.
151
- attr_accessor :weight
106
+ attr :dependency
152
107
 
153
108
  attr :local_window
154
109
  attr :remote_window
155
110
 
156
- def add_child(stream)
157
- @children ||= {}
158
- @children[stream.id] = stream
111
+ def weight
112
+ @dependency.weight
159
113
  end
160
114
 
161
- def remove_child(stream)
162
- @children.delete(stream.id)
115
+ def priority
116
+ @dependency.priority
163
117
  end
164
118
 
165
- def exclusive_child(stream)
166
- stream.children = @children
167
-
168
- @children.each_value do |child|
169
- child.dependent_id = stream.id
170
- end
171
-
172
- @children = {stream.id => stream}
173
-
174
- stream.dependent_id = @id
119
+ def priority= priority
120
+ @dependency.priority = priority
175
121
  end
176
122
 
177
- def parent(id = @dependent_id)
178
- @connection[id] || @connection.accept_stream(id)
123
+ def parent=(stream)
124
+ @dependency.parent = stream.dependency
179
125
  end
180
126
 
181
- def parent= stream
182
- self.parent&.remove_child(self)
183
-
184
- @dependent_id = stream.id
185
-
186
- stream.add_child(self)
187
- end
188
-
189
- def process_priority priority
190
- dependent_id = priority.stream_dependency
191
-
192
- if dependent_id == @id
193
- raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
194
- end
195
-
196
- @weight = priority.weight
197
-
198
- if priority.exclusive
199
- self.parent&.remove_child(self)
200
-
201
- self.parent(dependent_id).exclusive_child(self)
202
- elsif dependent_id != @dependent_id
203
- self.parent&.remove_child(self)
204
-
205
- @dependent_id = dependent_id
206
-
207
- self.parent.add_child(self)
208
- end
127
+ def children
128
+ @dependency&.streams
209
129
  end
210
130
 
211
131
  # The stream is being closed because the connection is being closed.
212
132
  def close(error = nil)
133
+ @connection.delete(@id)
213
134
  end
214
135
 
215
136
  def maximum_frame_size
@@ -315,7 +236,6 @@ module Protocol
315
236
  def open!
316
237
  if @state == :idle
317
238
  @state = :open
318
- @connection.activate(self)
319
239
  else
320
240
  raise ProtocolError, "Cannot open stream in state: #{@state}"
321
241
  end
@@ -328,9 +248,6 @@ module Protocol
328
248
  def close!(error_code = nil)
329
249
  @state = :closed
330
250
 
331
- @connection.deactivate(self)
332
- self.parent&.remove_child(self)
333
-
334
251
  if error_code
335
252
  error = StreamError.new("Stream closed!", error_code)
336
253
  end
@@ -353,7 +270,7 @@ module Protocol
353
270
  end
354
271
  end
355
272
 
356
- protected def process_headers(frame)
273
+ def process_headers(frame)
357
274
  # Receiving request headers:
358
275
  priority, data = frame.unpack
359
276
 
@@ -376,23 +293,23 @@ module Protocol
376
293
  open!
377
294
  end
378
295
 
379
- return process_headers(frame)
296
+ process_headers(frame)
380
297
  elsif @state == :reserved_remote
381
- @state = :half_closed_local
298
+ process_headers(frame)
382
299
 
383
- return process_headers(frame)
300
+ @state = :half_closed_local
384
301
  elsif @state == :open
302
+ process_headers(frame)
303
+
385
304
  if frame.end_stream?
386
305
  @state = :half_closed_remote
387
306
  end
388
-
389
- return process_headers(frame)
390
307
  elsif @state == :half_closed_local
308
+ process_headers(frame)
309
+
391
310
  if frame.end_stream?
392
311
  close!
393
312
  end
394
-
395
- return process_headers(frame)
396
313
  elsif self.closed?
397
314
  ignore_headers(frame)
398
315
  else
@@ -435,25 +352,6 @@ module Protocol
435
352
  end
436
353
  end
437
354
 
438
- # Change the priority of the stream both locally and remotely.
439
- def priority= priority
440
- send_priority(priority)
441
- process_priority(priority)
442
- end
443
-
444
- # The current local priority of the stream.
445
- def priority(exclusive = false)
446
- Priority.new(exclusive, @dependent_id, @weight)
447
- end
448
-
449
- def send_priority(priority)
450
- @connection.send_priority(@id, priority)
451
- end
452
-
453
- def receive_priority(frame)
454
- self.process_priority(frame.unpack)
455
- end
456
-
457
355
  def ignore_reset_stream(frame)
458
356
  # Async.logger.warn(self) {"Received reset stream (#{error_code}) in state: #{@state}!"}
459
357
  end
@@ -493,7 +391,6 @@ module Protocol
493
391
  def reserved_local!
494
392
  if @state == :idle
495
393
  @state = :reserved_local
496
- @connection.activate(self)
497
394
  else
498
395
  raise ProtocolError, "Cannot reserve stream in state: #{@state}"
499
396
  end
@@ -502,7 +399,6 @@ module Protocol
502
399
  def reserved_remote!
503
400
  if @state == :idle
504
401
  @state = :reserved_remote
505
- @connection.activate(self)
506
402
  else
507
403
  raise ProtocolError, "Cannot reserve stream in state: #{@state}"
508
404
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Protocol
22
22
  module HTTP2
23
- VERSION = "0.10.4"
23
+ VERSION = "0.11.0"
24
24
  end
25
25
  end
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.10.4
4
+ version: 0.11.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-12-08 00:00:00.000000000 Z
11
+ date: 2020-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: protocol-hpack
@@ -114,6 +114,7 @@ files:
114
114
  - lib/protocol/http2/connection.rb
115
115
  - lib/protocol/http2/continuation_frame.rb
116
116
  - lib/protocol/http2/data_frame.rb
117
+ - lib/protocol/http2/dependency.rb
117
118
  - lib/protocol/http2/error.rb
118
119
  - lib/protocol/http2/extensions/sum.rb
119
120
  - lib/protocol/http2/extensions/unpack.rb
@@ -152,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
153
  - !ruby/object:Gem::Version
153
154
  version: '0'
154
155
  requirements: []
155
- rubygems_version: 3.0.6
156
+ rubygems_version: 3.1.2
156
157
  signing_key:
157
158
  specification_version: 4
158
159
  summary: A low level implementation of the HTTP/2 protocol.