http-2 0.11.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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