http-2 0.11.0 → 1.0.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -9
  3. data/lib/http/2/base64.rb +45 -0
  4. data/lib/http/2/client.rb +19 -6
  5. data/lib/http/2/connection.rb +235 -163
  6. data/lib/http/2/emitter.rb +7 -5
  7. data/lib/http/2/error.rb +24 -1
  8. data/lib/http/2/extensions.rb +53 -0
  9. data/lib/http/2/flow_buffer.rb +91 -33
  10. data/lib/http/2/framer.rb +184 -157
  11. data/lib/http/2/header/compressor.rb +157 -0
  12. data/lib/http/2/header/decompressor.rb +144 -0
  13. data/lib/http/2/header/encoding_context.rb +337 -0
  14. data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
  15. data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
  16. data/lib/http/2/header.rb +35 -0
  17. data/lib/http/2/server.rb +47 -20
  18. data/lib/http/2/stream.rb +130 -61
  19. data/lib/http/2/version.rb +3 -1
  20. data/lib/http/2.rb +14 -13
  21. data/sig/client.rbs +9 -0
  22. data/sig/connection.rbs +93 -0
  23. data/sig/emitter.rbs +13 -0
  24. data/sig/error.rbs +35 -0
  25. data/sig/extensions.rbs +5 -0
  26. data/sig/flow_buffer.rbs +21 -0
  27. data/sig/frame_buffer.rbs +13 -0
  28. data/sig/framer.rbs +54 -0
  29. data/sig/header/compressor.rbs +27 -0
  30. data/sig/header/decompressor.rbs +22 -0
  31. data/sig/header/encoding_context.rbs +34 -0
  32. data/sig/header/huffman.rbs +9 -0
  33. data/sig/header.rbs +27 -0
  34. data/sig/next.rbs +101 -0
  35. data/sig/server.rbs +12 -0
  36. data/sig/stream.rbs +91 -0
  37. metadata +38 -79
  38. data/.autotest +0 -20
  39. data/.coveralls.yml +0 -1
  40. data/.gitignore +0 -20
  41. data/.gitmodules +0 -3
  42. data/.rspec +0 -5
  43. data/.rubocop.yml +0 -93
  44. data/.rubocop_todo.yml +0 -131
  45. data/.travis.yml +0 -17
  46. data/Gemfile +0 -16
  47. data/Guardfile +0 -18
  48. data/Guardfile.h2spec +0 -12
  49. data/LICENSE +0 -21
  50. data/Rakefile +0 -49
  51. data/example/Gemfile +0 -3
  52. data/example/README.md +0 -44
  53. data/example/client.rb +0 -122
  54. data/example/helper.rb +0 -19
  55. data/example/keys/server.crt +0 -20
  56. data/example/keys/server.key +0 -27
  57. data/example/server.rb +0 -139
  58. data/example/upgrade_client.rb +0 -153
  59. data/example/upgrade_server.rb +0 -203
  60. data/http-2.gemspec +0 -22
  61. data/lib/http/2/buffer.rb +0 -76
  62. data/lib/http/2/compressor.rb +0 -572
  63. data/lib/tasks/generate_huffman_table.rb +0 -166
  64. data/spec/buffer_spec.rb +0 -28
  65. data/spec/client_spec.rb +0 -188
  66. data/spec/compressor_spec.rb +0 -666
  67. data/spec/connection_spec.rb +0 -681
  68. data/spec/emitter_spec.rb +0 -54
  69. data/spec/framer_spec.rb +0 -487
  70. data/spec/h2spec/h2spec.darwin +0 -0
  71. data/spec/h2spec/output/non_secure.txt +0 -317
  72. data/spec/helper.rb +0 -147
  73. data/spec/hpack_test_spec.rb +0 -84
  74. data/spec/huffman_spec.rb +0 -68
  75. data/spec/server_spec.rb +0 -52
  76. data/spec/stream_spec.rb +0 -878
  77. data/spec/support/deep_dup.rb +0 -55
  78. data/spec/support/duplicable.rb +0 -98
@@ -1,4 +1,7 @@
1
- require_relative 'error'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../error"
4
+ require_relative "../extensions"
2
5
 
3
6
  module HTTP2
4
7
  # Implementation of huffman encoding for HPACK
@@ -7,7 +10,10 @@ module HTTP2
7
10
  module Header
8
11
  # Huffman encoder/decoder
9
12
  class Huffman
13
+ using StringExtensions
14
+
10
15
  include Error
16
+ include PackingExtensions
11
17
 
12
18
  BITS_AT_ONCE = 4
13
19
  EOS = 256
@@ -20,8 +26,8 @@ module HTTP2
20
26
  # @return [String] binary string
21
27
  def encode(str)
22
28
  bitstring = str.each_byte.map { |chr| ENCODE_TABLE[chr] }.join
23
- bitstring << '1' * ((8 - bitstring.size) % 8)
24
- [bitstring].pack('B*')
29
+ bitstring << ("1" * ((8 - bitstring.size) % 8))
30
+ [bitstring].pack("B*")
25
31
  end
26
32
 
27
33
  # Decodes provided Huffman coded string.
@@ -30,12 +36,12 @@ module HTTP2
30
36
  # @return [String] binary string
31
37
  # @raise [CompressionError] when Huffman coded string is malformed
32
38
  def decode(buf)
33
- emit = ''
39
+ emit = "".b
34
40
  state = 0 # start state
35
41
 
36
42
  mask = (1 << BITS_AT_ONCE) - 1
37
43
  buf.each_byte do |chr|
38
- (8 / BITS_AT_ONCE - 1).downto(0) do |shift|
44
+ ((8 / BITS_AT_ONCE) - 1).downto(0) do |shift|
39
45
  branch = (chr >> (shift * BITS_AT_ONCE)) & mask
40
46
  # MACHINE[state] = [final, [transitions]]
41
47
  # [final] unfinished bits so far are prefix of the EOS code.
@@ -43,15 +49,15 @@ module HTTP2
43
49
  # [emit] character to be emitted on this transition, empty string, or EOS.
44
50
  # [next] next state number.
45
51
  trans = MACHINE[state][branch]
46
- fail CompressionError, 'Huffman decode error (EOS found)' if trans.first == EOS
52
+ raise CompressionError, "Huffman decode error (EOS found)" if trans.first == EOS
53
+
47
54
  emit << trans.first.chr if trans.first
48
55
  state = trans.last
49
56
  end
50
57
  end
51
58
  # Check whether partial input is correctly filled
52
- unless state <= MAX_FINAL_STATE
53
- fail CompressionError, 'Huffman decode error (EOS invalid)'
54
- end
59
+ raise CompressionError, "Huffman decode error (EOS invalid)" unless state <= MAX_FINAL_STATE
60
+
55
61
  emit.force_encoding(Encoding::BINARY)
56
62
  end
57
63
 
@@ -153,23 +159,23 @@ module HTTP2
153
159
  [0x7fff0, 19],
154
160
  [0x1ffc, 13],
155
161
  [0x3ffc, 14],
156
- [0x22, 6],
162
+ [0x22, 6],
157
163
  [0x7ffd, 15],
158
- [0x3, 5],
159
- [0x23, 6],
160
- [0x4, 5],
161
- [0x24, 6],
162
- [0x5, 5],
164
+ [0x3, 5],
165
+ [0x23, 6],
166
+ [0x4, 5],
167
+ [0x24, 6],
168
+ [0x5, 5],
163
169
  [0x25, 6],
164
170
  [0x26, 6],
165
171
  [0x27, 6],
166
- [0x6, 5],
172
+ [0x6, 5],
167
173
  [0x74, 7],
168
174
  [0x75, 7],
169
175
  [0x28, 6],
170
176
  [0x29, 6],
171
177
  [0x2a, 6],
172
- [0x7, 5],
178
+ [0x7, 5],
173
179
  [0x2b, 6],
174
180
  [0x76, 7],
175
181
  [0x2c, 6],
@@ -314,10 +320,10 @@ module HTTP2
314
320
  [0x7ffffef, 27],
315
321
  [0x7fffff0, 27],
316
322
  [0x3ffffee, 26],
317
- [0x3fffffff, 30],
323
+ [0x3fffffff, 30]
318
324
  ].each(&:freeze).freeze
319
325
 
320
- ENCODE_TABLE = CODES.map { |c, l| [c].pack('N').unpack('B*').first[-l..-1] }.each(&:freeze).freeze
326
+ ENCODE_TABLE = CODES.map { |c, l| [c].pack("N").unpack1("B*")[-l..-1] }.each(&:freeze).freeze
321
327
  end
322
328
  end
323
329
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Machine generated Huffman decoder state machine.
2
4
  # DO NOT EDIT THIS FILE.
3
5
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP2
4
+ # Implementation of header compression for HTTP 2.0 (HPACK) format adapted
5
+ # to efficiently represent HTTP headers in the context of HTTP 2.0.
6
+ #
7
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
8
+ module Header
9
+ # Header representation as defined by the spec.
10
+ HEADREP = {
11
+ indexed: { prefix: 7, pattern: 0x80 },
12
+ incremental: { prefix: 6, pattern: 0x40 },
13
+ noindex: { prefix: 4, pattern: 0x00 },
14
+ neverindexed: { prefix: 4, pattern: 0x10 },
15
+ changetablesize: { prefix: 5, pattern: 0x20 }
16
+ }.each_value(&:freeze).freeze
17
+
18
+ # Predefined options set for Compressor
19
+ # http://mew.org/~kazu/material/2014-hpack.pdf
20
+ NAIVE = { index: :never, huffman: :never }.freeze
21
+ LINEAR = { index: :all, huffman: :never }.freeze
22
+ STATIC = { index: :static, huffman: :never }.freeze
23
+ SHORTER = { index: :all, huffman: :never }.freeze
24
+ NAIVEH = { index: :never, huffman: :always }.freeze
25
+ LINEARH = { index: :all, huffman: :always }.freeze
26
+ STATICH = { index: :static, huffman: :always }.freeze
27
+ SHORTERH = { index: :all, huffman: :shorter }.freeze
28
+ end
29
+ end
30
+
31
+ require "http/2/header/huffman"
32
+ require "http/2/header/huffman_statemachine"
33
+ require "http/2/header/encoding_context"
34
+ require "http/2/header/compressor"
35
+ require "http/2/header/decompressor"
data/lib/http/2/server.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'base64'
1
+ # frozen_string_literal: true
2
+
2
3
  module HTTP2
3
4
  # HTTP 2.0 server connection class that implements appropriate header
4
5
  # compression / decompression algorithms and stream management logic.
@@ -20,14 +21,19 @@ module HTTP2
20
21
  # end
21
22
  #
22
23
  class Server < Connection
24
+ attr_reader :origin_set
25
+
23
26
  # Initialize new HTTP 2.0 server object.
24
- def initialize(**settings)
27
+ def initialize(settings = {})
25
28
  @stream_id = 2
26
29
  @state = :waiting_magic
27
30
 
28
31
  @local_role = :server
29
32
  @remote_role = :client
30
33
 
34
+ @origin_set = []
35
+ @origins_sent = true
36
+
31
37
  super
32
38
  end
33
39
 
@@ -72,14 +78,17 @@ module HTTP2
72
78
  receive(CONNECTION_PREFACE_MAGIC)
73
79
 
74
80
  # Process received HTTP2-Settings payload
75
- buf = HTTP2::Buffer.new Base64.urlsafe_decode64(settings.to_s)
76
- header = @framer.common_header(
77
- length: buf.bytesize,
78
- type: :settings,
79
- stream: 0,
80
- flags: [],
81
+ buf = "".b
82
+ buf << Base64.urlsafe_decode64(settings.to_s)
83
+ @framer.common_header(
84
+ {
85
+ length: buf.bytesize,
86
+ type: :settings,
87
+ stream: 0,
88
+ flags: []
89
+ },
90
+ buffer: buf
81
91
  )
82
- buf.prepend(header)
83
92
  receive(buf)
84
93
 
85
94
  # Activate stream (id: 1) with on HTTP/1.1 request parameters
@@ -87,16 +96,17 @@ module HTTP2
87
96
  emit(:stream, stream)
88
97
 
89
98
  headers_frame = {
90
- type: :headers,
91
- stream: 1,
92
- weight: DEFAULT_WEIGHT,
99
+ type: :headers,
100
+ flags: [:end_headers],
101
+ stream: 1,
102
+ weight: DEFAULT_WEIGHT,
93
103
  dependency: 0,
94
- exclusive: false,
95
- payload: headers,
104
+ exclusive: false,
105
+ payload: headers
96
106
  }
97
107
 
98
108
  if body.empty?
99
- headers_frame.merge!(flags: [:end_stream])
109
+ headers_frame[:flags] << [:end_stream]
100
110
  stream << headers_frame
101
111
  else
102
112
  stream << headers_frame
@@ -110,24 +120,41 @@ module HTTP2
110
120
  @state = :waiting_magic
111
121
  end
112
122
 
123
+ def origin_set=(origins)
124
+ @origin_set = Array(origins).map(&:to_s)
125
+ @origins_sent = @origin_set.empty?
126
+ end
127
+
113
128
  private
114
129
 
130
+ def connection_settings(frame)
131
+ super
132
+ return unless frame[:flags].include?(:ack) && !@origins_sent
133
+
134
+ send(type: :origin, stream: 0, payload: @origin_set)
135
+ end
136
+
137
+ def verify_pseudo_headers(frame)
138
+ _verify_pseudo_headers(frame, REQUEST_MANDATORY_HEADERS)
139
+ end
140
+
115
141
  # Handle locally initiated server-push event emitted by the stream.
116
142
  #
117
- # @param args [Array]
143
+ # @param parent [Stream]
144
+ # @param headers [Enumerable[String, String]]
145
+ # @param flags [Array[Symbol]]
118
146
  # @param callback [Proc]
119
- def promise(*args, &callback)
120
- parent, headers, flags = *args
147
+ def promise(parent, headers, flags)
121
148
  promise = new_stream(parent: parent)
122
149
  promise.send(
123
150
  type: :push_promise,
124
151
  flags: flags,
125
152
  stream: parent.id,
126
153
  promise_stream: promise.id,
127
- payload: headers.to_a,
154
+ payload: headers.to_a
128
155
  )
129
156
 
130
- callback.call(promise)
157
+ yield(promise)
131
158
  end
132
159
  end
133
160
  end
data/lib/http/2/stream.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP2
2
4
  # A single HTTP 2.0 connection can multiplex multiple streams in parallel:
3
5
  # multiple requests and responses can be in flight simultaneously and stream
@@ -49,11 +51,10 @@ module HTTP2
49
51
 
50
52
  # Stream priority as set by initiator.
51
53
  attr_reader :weight
52
- attr_reader :dependency
54
+ attr_reader :dependency, :remote_window
53
55
 
54
56
  # Size of current stream flow control window.
55
57
  attr_reader :local_window
56
- attr_reader :remote_window
57
58
  alias window local_window
58
59
 
59
60
  # Reason why connection was closed.
@@ -73,19 +74,24 @@ module HTTP2
73
74
  # @param parent [Stream]
74
75
  # @param state [Symbol]
75
76
  def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
77
+ stream_error(:protocol_error, msg: "stream can't depend on itself") if id == dependency
78
+
76
79
  @connection = connection
77
80
  @id = id
78
81
  @weight = weight
79
82
  @dependency = dependency
80
- process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
83
+ process_priority(weight: weight, dependency: dependency, exclusive: exclusive)
81
84
  @local_window_max_size = connection.local_settings[:settings_initial_window_size]
82
- @local_window = connection.local_settings[:settings_initial_window_size]
85
+ @local_window = connection.local_settings[:settings_initial_window_size]
83
86
  @remote_window = connection.remote_settings[:settings_initial_window_size]
84
87
  @parent = parent
85
88
  @state = state
86
89
  @error = false
87
90
  @closed = false
88
- @send_buffer = []
91
+ @_method = @_content_length = @_status_code = nil
92
+ @_waiting_on_trailers = false
93
+ @received_data = false
94
+ @activated = false
89
95
 
90
96
  on(:window) { |v| @remote_window = v }
91
97
  on(:local_window) { |v| @local_window_max_size = @local_window = v }
@@ -103,26 +109,52 @@ module HTTP2
103
109
 
104
110
  case frame[:type]
105
111
  when :data
112
+ # 6.1. DATA
113
+ # If a DATA frame is received whose stream is not in "open" or
114
+ # "half closed (local)" state, the recipient MUST respond with a
115
+ # stream error (Section 5.4.2) of type STREAM_CLOSED.
116
+ stream_error(:stream_closed) unless @state == :open ||
117
+ @state == :half_closed_local ||
118
+ @state == :half_closing || @state == :closing ||
119
+ (@state == :closed && @closed == :local_rst)
120
+ @received_data = true
121
+ calculate_content_length(frame[:length])
106
122
  update_local_window(frame)
107
123
  # Emit DATA frame
108
124
  emit(:data, frame[:payload]) unless frame[:ignore]
109
125
  calculate_window_update(@local_window_max_size)
110
126
  when :headers
127
+ stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) ||
128
+ @state == :remote_closed
129
+ @_method ||= frame[:method]
130
+ @_status_code ||= frame[:status]
131
+ @_content_length ||= frame[:content_length]
132
+ @_trailers ||= frame[:trailer]
133
+ if @_waiting_on_trailers ||
134
+ (@received_data &&
135
+ (!@_status_code || @_status_code >= 200))
136
+
137
+ # An endpoint that receives a HEADERS frame without the END_STREAM flag set after receiving a final
138
+ # (non-informational) status code MUST treat the corresponding request or response as malformed.
139
+ verify_trailers(frame)
140
+ end
111
141
  emit(:headers, frame[:payload]) unless frame[:ignore]
142
+ @_waiting_on_trailers = !@_trailers.nil?
112
143
  when :push_promise
113
144
  emit(:promise_headers, frame[:payload]) unless frame[:ignore]
145
+ when :continuation
146
+ stream_error(:stream_closed) if (@state == :closed && @closed != :local_rst) || @state == :remote_closed
147
+ stream_error(:protocol_error) if @received_data
114
148
  when :priority
115
149
  process_priority(frame)
116
150
  when :window_update
117
- process_window_update(frame)
151
+ process_window_update(frame: frame)
118
152
  when :altsvc
119
153
  # 4. The ALTSVC HTTP/2 Frame
120
154
  # An ALTSVC frame on a
121
155
  # stream other than stream 0 containing non-empty "Origin" information
122
156
  # is invalid and MUST be ignored.
123
- if !frame[:origin] || frame[:origin].empty?
124
- emit(frame[:type], frame)
125
- end
157
+ emit(frame[:type], frame) if !frame[:origin] || frame[:origin].empty?
126
158
  when :blocked
127
159
  emit(frame[:type], frame)
128
160
  end
@@ -131,6 +163,29 @@ module HTTP2
131
163
  end
132
164
  alias << receive
133
165
 
166
+ def verify_trailers(frame)
167
+ stream_error(:protocol_error, msg: "trailer headers frame must close the stream") unless end_stream?(frame)
168
+ return unless @_trailers
169
+
170
+ trailers = frame[:payload]
171
+ return unless trailers.respond_to?(:each)
172
+
173
+ trailers.each do |field, _| # rubocop:disable Style/HashEachMethods
174
+ @_trailers.delete(field)
175
+ break if @_trailers.empty?
176
+ end
177
+ stream_error(:protocol_error, msg: "didn't receive all expected trailer headers") unless @_trailers.empty?
178
+ end
179
+
180
+ def calculate_content_length(data_length)
181
+ return unless @_content_length && data_length
182
+
183
+ @_content_length -= data_length
184
+ return if @_content_length >= 0
185
+
186
+ stream_error(:protocol_error, msg: "received more data than what was defined in content-length")
187
+ end
188
+
134
189
  # Processes outgoing HTTP 2.0 frames. Data frames may be automatically
135
190
  # split and buffered based on maximum frame size and current stream flow
136
191
  # control window size.
@@ -164,13 +219,13 @@ module HTTP2
164
219
  def headers(headers, end_headers: true, end_stream: false)
165
220
  flags = []
166
221
  flags << :end_headers if end_headers
167
- flags << :end_stream if end_stream
222
+ flags << :end_stream if end_stream || @_method == "HEAD"
168
223
 
169
224
  send(type: :headers, flags: flags, payload: headers)
170
225
  end
171
226
 
172
227
  def promise(headers, end_headers: true, &block)
173
- fail ArgumentError, 'must provide callback' unless block_given?
228
+ raise ArgumentError, "must provide callback" unless block
174
229
 
175
230
  flags = end_headers ? [:end_headers] : []
176
231
  emit(:promise, self, headers, flags, &block)
@@ -183,7 +238,7 @@ module HTTP2
183
238
  # @param dependency [Integer] new stream dependency stream
184
239
  def reprioritize(weight: 16, dependency: 0, exclusive: false)
185
240
  stream_error if @id.even?
186
- send(type: :priority, weight: weight, stream_dependency: dependency, exclusive: exclusive)
241
+ send(type: :priority, weight: weight, dependency: dependency, exclusive: exclusive)
187
242
  end
188
243
 
189
244
  # Sends DATA frame containing response payload.
@@ -243,6 +298,7 @@ module HTTP2
243
298
  def window_update(increment)
244
299
  # emit stream-level WINDOW_UPDATE unless stream is closed
245
300
  return if @state == :closed || @state == :remote_closed
301
+
246
302
  send(type: :window_update, increment: increment)
247
303
  end
248
304
 
@@ -345,18 +401,18 @@ module HTTP2
345
401
  # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
346
402
  when :reserved_local
347
403
  @state = if sending
348
- case frame[:type]
349
- when :headers then event(:half_closed_remote)
350
- when :rst_stream then event(:local_rst)
351
- else stream_error
352
- end
353
- else
354
- case frame[:type]
355
- when :rst_stream then event(:remote_rst)
356
- when :priority, :window_update then @state
357
- else stream_error
358
- end
359
- end
404
+ case frame[:type]
405
+ when :headers then event(:half_closed_remote)
406
+ when :rst_stream then event(:local_rst)
407
+ else stream_error
408
+ end
409
+ else
410
+ case frame[:type]
411
+ when :rst_stream then event(:remote_rst)
412
+ when :priority, :window_update then @state
413
+ else stream_error
414
+ end
415
+ end
360
416
 
361
417
  # A stream in the "reserved (remote)" state has been reserved by a
362
418
  # remote peer.
@@ -374,18 +430,18 @@ module HTTP2
374
430
  # error (Section 5.4.1) of type PROTOCOL_ERROR.
375
431
  when :reserved_remote
376
432
  @state = if sending
377
- case frame[:type]
378
- when :rst_stream then event(:local_rst)
379
- when :priority, :window_update then @state
380
- else stream_error
381
- end
382
- else
383
- case frame[:type]
384
- when :headers then event(:half_closed_local)
385
- when :rst_stream then event(:remote_rst)
386
- else stream_error
387
- end
388
- end
433
+ case frame[:type]
434
+ when :rst_stream then event(:local_rst)
435
+ when :priority, :window_update then @state
436
+ else stream_error
437
+ end
438
+ else
439
+ case frame[:type]
440
+ when :headers then event(:half_closed_local)
441
+ when :rst_stream then event(:remote_rst)
442
+ else stream_error
443
+ end
444
+ end
389
445
 
390
446
  # A stream in the "open" state may be used by both peers to send
391
447
  # frames of any type. In this state, sending peers observe
@@ -523,26 +579,24 @@ module HTTP2
523
579
  when :closed
524
580
  if sending
525
581
  case frame[:type]
526
- when :rst_stream then # ignore
527
- when :priority then
582
+ when :rst_stream # ignore
583
+ when :priority
528
584
  process_priority(frame)
529
585
  else
530
- stream_error(:stream_closed) unless (frame[:type] == :rst_stream)
586
+ stream_error(:stream_closed) unless frame[:type] == :rst_stream
531
587
  end
588
+ elsif frame[:type] == :priority
589
+ process_priority(frame)
532
590
  else
533
- if frame[:type] == :priority
534
- process_priority(frame)
535
- else
536
- case @closed
537
- when :remote_rst, :remote_closed
538
- case frame[:type]
539
- when :rst_stream, :window_update # nop here
540
- else
541
- stream_error(:stream_closed)
542
- end
543
- when :local_rst, :local_closed
544
- frame[:ignore] = true if frame[:type] != :window_update
591
+ case @closed
592
+ when :remote_rst, :remote_closed
593
+ case frame[:type]
594
+ when :rst_stream, :window_update # nop here
595
+ else
596
+ stream_error(:stream_closed)
545
597
  end
598
+ when :local_rst, :local_closed
599
+ frame[:ignore] = true if frame[:type] != :window_update
546
600
  end
547
601
  end
548
602
  end
@@ -552,7 +606,7 @@ module HTTP2
552
606
  case newstate
553
607
  when :open
554
608
  @state = newstate
555
- emit(:active)
609
+ activate_stream_in_conn
556
610
 
557
611
  when :reserved_local, :reserved_remote
558
612
  @state = newstate
@@ -560,7 +614,7 @@ module HTTP2
560
614
 
561
615
  when :half_closed_local, :half_closed_remote
562
616
  @closed = newstate
563
- emit(:active) unless @state == :open
617
+ activate_stream_in_conn unless @state == :open
564
618
  @state = :half_closing
565
619
 
566
620
  when :local_closed, :remote_closed, :local_rst, :remote_rst
@@ -571,11 +625,25 @@ module HTTP2
571
625
  @state
572
626
  end
573
627
 
628
+ # Streams that are in the "open" state, or either of the "half closed"
629
+ # states count toward the maximum number of streams that an endpoint is
630
+ # permitted to open.
631
+ def activate_stream_in_conn
632
+ @connection.active_stream_count += 1
633
+ @activated = true
634
+ emit(:active)
635
+ end
636
+
637
+ def close_stream_in_conn(*args)
638
+ @connection.active_stream_count -= 1 if @activated
639
+ emit(:close, *args)
640
+ end
641
+
574
642
  def complete_transition(frame)
575
643
  case @state
576
644
  when :closing
577
645
  @state = :closed
578
- emit(:close, frame[:error])
646
+ close_stream_in_conn(frame[:error])
579
647
  when :half_closing
580
648
  @state = @closed
581
649
  emit(:half_close)
@@ -584,12 +652,12 @@ module HTTP2
584
652
 
585
653
  def process_priority(frame)
586
654
  @weight = frame[:weight]
587
- @dependency = frame[:stream_dependency]
655
+ @dependency = frame[:dependency]
588
656
  emit(
589
657
  :priority,
590
- weight: frame[:weight],
591
- dependency: frame[:stream_dependency],
592
- exclusive: frame[:exclusive],
658
+ weight: frame[:weight],
659
+ dependency: frame[:dependency],
660
+ exclusive: frame[:exclusive]
593
661
  )
594
662
  # TODO: implement dependency tree housekeeping
595
663
  # Latest draft defines a fairly complex priority control.
@@ -601,17 +669,18 @@ module HTTP2
601
669
  def end_stream?(frame)
602
670
  case frame[:type]
603
671
  when :data, :headers, :continuation
604
- frame[:flags].include?(:end_stream)
672
+ frame[:flags] && frame[:flags].include?(:end_stream)
605
673
  else false
606
674
  end
607
675
  end
608
676
 
609
677
  def stream_error(error = :internal_error, msg: nil)
678
+ # if the stream already broke with an error, ignore subsequent
679
+
610
680
  @error = error
611
681
  close(error) if @state != :closed
612
682
 
613
- klass = error.to_s.split('_').map(&:capitalize).join
614
- fail Error.const_get(klass), msg
683
+ raise Error.types[error], msg
615
684
  end
616
685
  alias error stream_error
617
686
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP2
2
- VERSION = '0.11.0'.freeze
4
+ VERSION = "1.0.0"
3
5
  end
data/lib/http/2.rb CHANGED
@@ -1,13 +1,14 @@
1
- require 'http/2/version'
2
- require 'http/2/error'
3
- require 'http/2/emitter'
4
- require 'http/2/buffer'
5
- require 'http/2/flow_buffer'
6
- require 'http/2/huffman'
7
- require 'http/2/huffman_statemachine'
8
- require 'http/2/compressor'
9
- require 'http/2/framer'
10
- require 'http/2/connection'
11
- require 'http/2/client'
12
- require 'http/2/server'
13
- require 'http/2/stream'
1
+ # frozen_string_literal: true
2
+
3
+ require "http/2/version"
4
+ require "http/2/extensions"
5
+ require "http/2/base64"
6
+ require "http/2/error"
7
+ require "http/2/emitter"
8
+ require "http/2/flow_buffer"
9
+ require "http/2/header"
10
+ require "http/2/framer"
11
+ require "http/2/connection"
12
+ require "http/2/client"
13
+ require "http/2/server"
14
+ require "http/2/stream"
data/sig/client.rbs ADDED
@@ -0,0 +1,9 @@
1
+ module HTTP2
2
+ class Client < Connection
3
+ def upgrade: () -> Stream
4
+
5
+ def send_connection_preface: () -> void
6
+
7
+ def self.settings_header: (settings_enum) -> String
8
+ end
9
+ end