protocol-http2 0.10.4 → 0.11.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: 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.