http-2 1.0.2 → 1.1.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.
@@ -13,6 +13,8 @@ module HTTP2
13
13
  include Error
14
14
  include BufferUtils
15
15
 
16
+ FORBIDDEN_HEADERS = %w[connection te].freeze
17
+
16
18
  # @param options [Hash] decoding options. Only :table_size is effective.
17
19
  def initialize(options = {})
18
20
  @cc = EncodingContext.new(options)
@@ -30,17 +32,23 @@ module HTTP2
30
32
  # @param n [Integer] number of available bits
31
33
  # @return [Integer]
32
34
  def integer(buf, n)
33
- limit = (2**n) - 1
35
+ limit = (1 << n) - 1
34
36
  i = n.zero? ? 0 : (shift_byte(buf) & limit)
35
37
 
36
38
  m = 0
37
39
  if i == limit
38
- while (byte = shift_byte(buf))
40
+ offset = 0
41
+
42
+ buf.each_byte.with_index do |byte, idx|
43
+ offset = idx
44
+ # while (byte = shift_byte(buf))
39
45
  i += ((byte & 127) << m)
40
46
  m += 7
41
47
 
42
48
  break if byte.nobits?(128)
43
49
  end
50
+
51
+ read_str(buf, offset + 1)
44
52
  end
45
53
 
46
54
  i
@@ -59,7 +67,7 @@ module HTTP2
59
67
  str = read_str(buf, len)
60
68
  raise CompressionError, "string too short" unless str.bytesize == len
61
69
 
62
- str = Huffman.new.decode(str) if huffman
70
+ str = Huffman.decode(str) if huffman
63
71
  str.force_encoding(Encoding::UTF_8)
64
72
  end
65
73
 
@@ -70,37 +78,36 @@ module HTTP2
70
78
  def header(buf)
71
79
  peek = buf.getbyte(0)
72
80
 
73
- header = {}
74
- header[:type], type = HEADREP.find do |_t, desc|
81
+ header_type, type = HEADREP.find do |_, desc|
75
82
  mask = (peek >> desc[:prefix]) << desc[:prefix]
76
83
  mask == desc[:pattern]
77
84
  end
78
85
 
79
- raise CompressionError unless header[:type]
86
+ raise CompressionError unless header_type && type
80
87
 
81
- header[:name] = integer(buf, type[:prefix])
88
+ header_name = integer(buf, type[:prefix])
82
89
 
83
- case header[:type]
90
+ case header_type
84
91
  when :indexed
85
- raise CompressionError if (header[:name]).zero?
92
+ raise CompressionError if header_name.zero?
93
+
94
+ header_name -= 1
86
95
 
87
- header[:name] -= 1
96
+ { type: header_type, name: header_name }
88
97
  when :changetablesize
89
- header[:value] = header[:name]
98
+ { type: header_type, name: header_name, value: header_name }
90
99
  else
91
- if (header[:name]).zero?
92
- header[:name] = string(buf)
100
+ if header_name.zero?
101
+ header_name = string(buf)
93
102
  else
94
- header[:name] -= 1
103
+ header_name -= 1
95
104
  end
96
- header[:value] = string(buf)
97
- end
105
+ header_value = string(buf)
98
106
 
99
- header
107
+ { type: header_type, name: header_name, value: header_value }
108
+ end
100
109
  end
101
110
 
102
- FORBIDDEN_HEADERS = %w[connection te].freeze
103
-
104
111
  # Decodes and processes header commands within provided buffer.
105
112
  #
106
113
  # @param buf [Buffer]
@@ -114,7 +121,7 @@ module HTTP2
114
121
  field, value = @cc.process(header(buf))
115
122
  next if field.nil?
116
123
 
117
- is_pseudo_header = field.start_with? ":"
124
+ is_pseudo_header = field.start_with?(":")
118
125
  if !decoding_pseudo_headers && is_pseudo_header
119
126
  raise ProtocolError, "one or more pseudo headers encountered after regular headers"
120
127
  end
@@ -86,6 +86,16 @@ module HTTP2
86
86
 
87
87
  STATIC_TABLE_SIZE = STATIC_TABLE.size
88
88
 
89
+ DEFAULT_OPTIONS = {
90
+ huffman: :shorter,
91
+ index: :all,
92
+ table_size: 4096
93
+ }.freeze
94
+
95
+ STATIC_ALL = %i[all static].freeze
96
+
97
+ STATIC_NEVER = %i[never static].freeze
98
+
89
99
  # Current table of header key-value pairs.
90
100
  attr_reader :table
91
101
 
@@ -96,6 +106,9 @@ module HTTP2
96
106
  # :index Symbol :all, :static, :never
97
107
  attr_reader :options
98
108
 
109
+ # Current table size in octets
110
+ attr_reader :current_table_size
111
+
99
112
  # Initializes compression context with appropriate client/server
100
113
  # defaults and maximum size of the dynamic table.
101
114
  #
@@ -104,15 +117,11 @@ module HTTP2
104
117
  # :huffman Symbol :always, :never, :shorter
105
118
  # :index Symbol :all, :static, :never
106
119
  def initialize(options = {})
107
- default_options = {
108
- huffman: :shorter,
109
- index: :all,
110
- table_size: 4096
111
- }
112
120
  @table = []
113
- @options = default_options.merge(options)
121
+ @options = DEFAULT_OPTIONS.merge(options)
114
122
  @limit = @options[:table_size]
115
123
  @_table_updated = false
124
+ @current_table_size = 0
116
125
  end
117
126
 
118
127
  # Duplicates current compression context
@@ -140,10 +149,13 @@ module HTTP2
140
149
  # @return [Array] +[key, value]+
141
150
  def dereference(index)
142
151
  # NOTE: index is zero-based in this module.
143
- value = STATIC_TABLE[index] || @table[index - STATIC_TABLE_SIZE]
144
- raise CompressionError, "Index too large" unless value
152
+ return STATIC_TABLE[index] if index < STATIC_TABLE_SIZE
153
+
154
+ idx = index - STATIC_TABLE_SIZE
155
+
156
+ raise CompressionError, "Index too large" if idx >= @table.size
145
157
 
146
- value
158
+ @table[index - STATIC_TABLE_SIZE]
147
159
  end
148
160
 
149
161
  # Header Block Processing
@@ -153,9 +165,11 @@ module HTTP2
153
165
  # @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
154
166
  # or nil if +cmd[:type]+ is +:changetablesize+
155
167
  def process(cmd)
156
- emit = nil
168
+ type = cmd[:type]
169
+ name = cmd[:name]
170
+ value = cmd[:value]
157
171
 
158
- case cmd[:type]
172
+ case type
159
173
  when :changetablesize
160
174
  raise CompressionError, "tried to change table size after adding elements to table" if @_table_updated
161
175
 
@@ -163,21 +177,18 @@ module HTTP2
163
177
  # we should blow up if we receive another frame where the new table size is bigger.
164
178
  table_size_updated = @limit != @options[:table_size]
165
179
 
166
- raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && cmd[:value] > @limit
180
+ raise CompressionError, "dynamic table size update exceed limit" if !table_size_updated && value > @limit
167
181
 
168
- self.table_size = cmd[:value]
182
+ self.table_size = value
169
183
 
184
+ nil
170
185
  when :indexed
171
186
  # Indexed Representation
172
187
  # An _indexed representation_ entails the following actions:
173
188
  # o The header field corresponding to the referenced entry in either
174
189
  # the static table or dynamic table is added to the decoded header
175
190
  # list.
176
- idx = cmd[:name]
177
-
178
- k, v = dereference(idx)
179
- emit = [k, v]
180
-
191
+ dereference(name)
181
192
  when :incremental, :noindex, :neverindexed
182
193
  # A _literal representation_ that is _not added_ to the dynamic table
183
194
  # entails the following action:
@@ -188,27 +199,28 @@ module HTTP2
188
199
  # o The header field is added to the decoded header list.
189
200
  # o The header field is inserted at the beginning of the dynamic table.
190
201
 
191
- case cmd[:name]
202
+ case name
192
203
  when Integer
193
- k, v = dereference(cmd[:name])
204
+ name, v = dereference(name)
194
205
 
195
- cmd = cmd.dup
196
- cmd[:index] ||= cmd[:name]
197
- cmd[:value] ||= v
198
- cmd[:name] = k
206
+ value ||= v
199
207
  when UPPER
200
- raise ProtocolError, "Invalid uppercase key: #{cmd[:name]}"
208
+ raise ProtocolError, "Invalid uppercase key: #{name}"
201
209
  end
202
210
 
203
- emit = [cmd[:name], cmd[:value]]
211
+ emit = [name, value]
204
212
 
205
- add_to_table(emit) if cmd[:type] == :incremental
213
+ # add to table
214
+ if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
215
+ @table.unshift(emit)
216
+ @current_table_size += name.bytesize + value.bytesize + 32
217
+ @_table_updated = true
218
+ end
206
219
 
220
+ emit
207
221
  else
208
- raise CompressionError, "Invalid type: #{cmd[:type]}"
222
+ raise CompressionError, "Invalid type: #{type}"
209
223
  end
210
-
211
- emit
212
224
  end
213
225
 
214
226
  # Plan header compression according to +@options [:index]+
@@ -219,9 +231,9 @@ module HTTP2
219
231
  # @param headers [Array] +[[name, value], ...]+
220
232
  # @return [Array] array of commands
221
233
  def encode(headers)
222
- commands = []
223
234
  # Literals commands are marked with :noindex when index is not used
224
- noindex = %i[static never].include?(@options[:index])
235
+ noindex = STATIC_NEVER.include?(@options[:index])
236
+
225
237
  headers.each do |field, value|
226
238
  # Literal header names MUST be translated to lowercase before
227
239
  # encoding and transmission.
@@ -229,10 +241,9 @@ module HTTP2
229
241
  value = "/" if field == ":path" && value.empty?
230
242
  cmd = addcmd(field, value)
231
243
  cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
232
- commands << cmd
233
244
  process(cmd)
245
+ yield cmd
234
246
  end
235
- commands
236
247
  end
237
248
 
238
249
  # Emits command for a header.
@@ -245,30 +256,36 @@ module HTTP2
245
256
  # :static Use static table only.
246
257
  # :all Use all of them.
247
258
  #
248
- # @param header [Array] +[name, value]+
259
+ # @param field [String] the header field
260
+ # @param value [String] the header value
249
261
  # @return [Hash] command
250
- def addcmd(*header)
262
+ def addcmd(field, value)
263
+ # @type var exact: Integer?
251
264
  exact = nil
265
+ # @type var name_only: Integer?
252
266
  name_only = nil
253
267
 
254
- if %i[all static].include?(@options[:index])
255
- field, value = header
256
- if (svalues = STATIC_TABLE_BY_FIELD[field])
257
- svalues.each do |i, svalue|
258
- name_only ||= i
259
- if svalue == value
260
- exact = i
261
- break
262
- end
268
+ index_type = @options[:index]
269
+
270
+ if STATIC_ALL.include?(index_type) &&
271
+ STATIC_TABLE_BY_FIELD.key?(field)
272
+ STATIC_TABLE_BY_FIELD[field].each do |i, svalue|
273
+ name_only ||= i
274
+ if value == svalue
275
+ exact = i
276
+ break
263
277
  end
264
278
  end
265
279
  end
266
- if [:all].include?(@options[:index]) && !exact
267
- @table.each_index do |i|
268
- if @table[i] == header
269
- exact ||= i + STATIC_TABLE_SIZE
280
+
281
+ if index_type == :all && !exact
282
+ @table.each_with_index do |(hfield, hvalue), i|
283
+ next unless field == hfield
284
+
285
+ if value == hvalue
286
+ exact = i + STATIC_TABLE_SIZE
270
287
  break
271
- elsif @table[i].first == header.first
288
+ else
272
289
  name_only ||= i + STATIC_TABLE_SIZE
273
290
  end
274
291
  end
@@ -276,10 +293,8 @@ module HTTP2
276
293
 
277
294
  if exact
278
295
  { name: exact, type: :indexed }
279
- elsif name_only
280
- { name: name_only, value: header.last, type: :incremental }
281
296
  else
282
- { name: header.first, value: header.last, type: :incremental }
297
+ { name: name_only || field, value: value, type: :incremental }
283
298
  end
284
299
  end
285
300
 
@@ -287,13 +302,7 @@ module HTTP2
287
302
  # When the size is reduced, some headers might be evicted.
288
303
  def table_size=(size)
289
304
  @limit = size
290
- size_check(nil)
291
- end
292
-
293
- # Returns current table size in octets
294
- # @return [Integer]
295
- def current_table_size
296
- @table.inject(0) { |r, (k, v)| r + k.bytesize + v.bytesize + 32 }
305
+ size_check(0)
297
306
  end
298
307
 
299
308
  def listen_on_table
@@ -304,32 +313,20 @@ module HTTP2
304
313
 
305
314
  private
306
315
 
307
- # Add a name-value pair to the dynamic table.
308
- # Older entries might have been evicted so that
309
- # the new entry fits in the dynamic table.
310
- #
311
- # @param cmd [Array] +[name, value]+
312
- def add_to_table(cmd)
313
- return unless size_check(cmd)
314
-
315
- @table.unshift(cmd)
316
- @_table_updated = true
317
- end
318
-
319
316
  # To keep the dynamic table size lower than or equal to @limit,
320
317
  # remove one or more entries at the end of the dynamic table.
321
318
  #
322
- # @param cmd [Hash]
319
+ # @param cmdsize [Integer]
323
320
  # @return [Boolean] whether +cmd+ fits in the dynamic table.
324
- def size_check(cmd)
325
- cursize = current_table_size
326
- cmdsize = cmd.nil? ? 0 : cmd[0].bytesize + cmd[1].bytesize + 32
321
+ def size_check(cmdsize)
322
+ unless @table.empty?
323
+ while @current_table_size + cmdsize > @limit
327
324
 
328
- while cursize + cmdsize > @limit
329
- break if @table.empty?
325
+ name, value = @table.pop
326
+ @current_table_size -= name.bytesize + value.bytesize + 32
327
+ break if @table.empty?
330
328
 
331
- e = @table.pop
332
- cursize -= e[0].bytesize + e[1].bytesize + 32
329
+ end
333
330
  end
334
331
 
335
332
  cmdsize <= @limit
@@ -9,9 +9,12 @@ module HTTP2
9
9
  # - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
10
10
  module Header
11
11
  # Huffman encoder/decoder
12
- class Huffman
12
+ module Huffman
13
+ module_function
14
+
13
15
  include Error
14
- include PackingExtensions
16
+ extend PackingExtensions
17
+ extend BufferUtils
15
18
 
16
19
  BITS_AT_ONCE = 4
17
20
  EOS = 256
@@ -21,11 +24,13 @@ module HTTP2
21
24
  # Length is not encoded in this method.
22
25
  #
23
26
  # @param str [String]
27
+ # @param buffer [String]
24
28
  # @return [String] binary string
25
- def encode(str)
26
- bitstring = str.each_byte.map { |chr| ENCODE_TABLE[chr] }.join
27
- bitstring << ("1" * ((8 - bitstring.size) % 8))
28
- [bitstring].pack("B*")
29
+ def encode(str, buffer = "".b)
30
+ bitstring = String.new("", encoding: Encoding::BINARY, capacity: (str.bytesize * 30) + ((8 - str.size) % 8))
31
+ str.each_byte { |chr| append_str(bitstring, ENCODE_TABLE[chr]) }
32
+ append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
33
+ pack([bitstring], "B*", buffer: buffer)
29
34
  end
30
35
 
31
36
  # Decodes provided Huffman coded string.
@@ -46,11 +51,10 @@ module HTTP2
46
51
  # Each transition is [emit, next]
47
52
  # [emit] character to be emitted on this transition, empty string, or EOS.
48
53
  # [next] next state number.
49
- trans = MACHINE[state][branch]
50
- raise CompressionError, "Huffman decode error (EOS found)" if trans.first == EOS
54
+ first, state = MACHINE.dig(state, branch)
55
+ raise CompressionError, "Huffman decode error (EOS found)" if first == EOS
51
56
 
52
- emit << trans.first.chr if trans.first
53
- state = trans.last
57
+ append_str(emit, first.chr) if first
54
58
  end
55
59
  end
56
60
  # Check whether partial input is correctly filled
@@ -8,7 +8,7 @@
8
8
 
9
9
  module HTTP2
10
10
  module Header
11
- class Huffman
11
+ module Huffman
12
12
  # :nodoc:
13
13
  MAX_FINAL_STATE = 7
14
14
  MACHINE = [
data/lib/http/2/server.rb CHANGED
@@ -79,7 +79,7 @@ module HTTP2
79
79
 
80
80
  # Process received HTTP2-Settings payload
81
81
  buf = "".b
82
- buf << Base64.urlsafe_decode64(settings.to_s)
82
+ append_str(buf, Base64.urlsafe_decode64(settings.to_s))
83
83
  @framer.common_header(
84
84
  {
85
85
  length: buf.bytesize,
@@ -120,6 +120,12 @@ module HTTP2
120
120
  @state = :waiting_magic
121
121
  end
122
122
 
123
+ def activate_stream(**)
124
+ super.tap do |stream|
125
+ stream.on(:promise, &method(:promise))
126
+ end
127
+ end
128
+
123
129
  def origin_set=(origins)
124
130
  @origin_set = Array(origins).map(&:to_s)
125
131
  @origins_sent = @origin_set.empty?
data/lib/http/2/stream.rb CHANGED
@@ -40,6 +40,8 @@ module HTTP2
40
40
  include Emitter
41
41
  include Error
42
42
 
43
+ STREAM_OPEN_STATES = %i[open half_closed_local half_closing closing].freeze
44
+
43
45
  # Stream ID (odd for client initiated streams, even otherwise).
44
46
  attr_reader :id
45
47
 
@@ -80,6 +82,11 @@ module HTTP2
80
82
  @id = id
81
83
  @weight = weight
82
84
  @dependency = dependency
85
+
86
+ # from mixins
87
+ @listeners = Hash.new { |hash, key| hash[key] = [] }
88
+ @send_buffer = FrameBuffer.new
89
+
83
90
  process_priority(weight: weight, dependency: dependency, exclusive: exclusive)
84
91
  @local_window_max_size = connection.local_settings[:settings_initial_window_size]
85
92
  @local_window = connection.local_settings[:settings_initial_window_size]
@@ -88,7 +95,7 @@ module HTTP2
88
95
  @state = state
89
96
  @error = false
90
97
  @closed = false
91
- @_method = @_content_length = @_status_code = nil
98
+ @_method = @_content_length = @_status_code = @_trailers = nil
92
99
  @_waiting_on_trailers = false
93
100
  @received_data = false
94
101
  @activated = false
@@ -113,9 +120,7 @@ module HTTP2
113
120
  # If a DATA frame is received whose stream is not in "open" or
114
121
  # "half closed (local)" state, the recipient MUST respond with a
115
122
  # 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 ||
123
+ stream_error(:stream_closed) unless STREAM_OPEN_STATES.include?(@state) ||
119
124
  (@state == :closed && @closed == :local_rst)
120
125
  @received_data = true
121
126
  calculate_content_length(frame[:length])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/http/2.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "http/2/version"
4
+
5
+ module HTTP2
6
+ EMPTY = [].freeze
7
+ end
8
+
4
9
  require "http/2/extensions"
5
10
  require "http/2/base64"
6
11
  require "http/2/error"
@@ -7,7 +7,18 @@ module HTTP2
7
7
 
8
8
  DEFAULT_MAX_CONCURRENT_STREAMS: Integer
9
9
 
10
- type settings_hash = Hash[Symbol, Integer]
10
+ EMPTY: []
11
+
12
+ type connection_opts = Hash[Symbol, untyped]
13
+
14
+ type settings_hash = {
15
+ settings_header_table_size: Integer,
16
+ settings_enable_push: Integer,
17
+ settings_max_concurrent_streams: Integer,
18
+ settings_initial_window_size: Integer,
19
+ settings_max_frame_size: Integer,
20
+ settings_max_header_list_size: Integer
21
+ }
11
22
 
12
23
  type settings_ary = Array[settings_enum]
13
24
 
@@ -30,44 +41,44 @@ module HTTP2
30
41
  # # FRAMES
31
42
  type frame_control_flags = Array[:end_headers | :end_stream]
32
43
 
44
+ type common_frame = { stream: Integer }
45
+
33
46
  # # HEADERS
34
- # type headers_frame = {
35
- # type: :headers, flags: frame_control_flags, stream: Integer, payload: Enumerable[header_pair],
36
- # ?method: Symbol, ?trailer: Array[String], ?content_length: Integer, ?padding: Integer
37
- # }
47
+ type headers_frame = common_frame & {
48
+ type: :headers, flags: frame_control_flags, payload: Enumerable[header_pair] | String,
49
+ ?method: Symbol, ?trailer: Array[String], ?content_length: Integer, ?padding: Integer
50
+ }
38
51
 
39
52
  # # DATA
40
- type data_frame = { type: :data, flags: frame_control_flags, stream: Integer, length: Integer, payload: String, padding: Integer }
41
- | { type: :data, flags: frame_control_flags, stream: Integer, length: Integer, payload: String }
42
- | { type: :data, flags: frame_control_flags, payload: String }
53
+ type data_frame = { type: :data, flags: frame_control_flags, ?length: Integer, payload: String, ?padding: Integer }
43
54
 
44
55
  # # PUSH_PROMISE
45
- # type push_promise_frame = { type: :push_promise, promise_stream: Integer, flags: frame_control_flags, stream: Integer, ?method: Symbol, ?trailer: Array[String], ?content_length: Integer, payload: Enumerable[header_pair], ?padding: Integer }
56
+ type push_promise_frame = { type: :push_promise, promise_stream: Integer, flags: frame_control_flags, ?method: Symbol, ?trailer: Array[String], ?content_length: Integer, payload: Enumerable[header_pair], ?padding: Integer }
46
57
 
47
58
  # # SETTINGS
48
- # type settings_frame = { type: :settings, stream: 0, payload: Array[[Symbol | Integer, Integer]] }
59
+ type settings_frame = { type: :settings, payload: Array[[Symbol | Integer, Integer]] }
49
60
 
50
61
  # # WINDOW_UPDATE
51
- # type window_update_frame = { type: :window_update, stream: Integer, increment: Integer }
62
+ type window_update_frame = { type: :window_update, increment: Integer }
52
63
 
53
64
  # # PRIORITY
54
- type priority_frame = { type: :priority, stream: Integer, dependency: Integer, exclusive: bool, weight: Integer }
65
+ type priority_frame = { dependency: Integer, exclusive: bool, weight: Integer }
55
66
 
56
67
  # # ALTSVC
57
- # type altsvc_frame = { type: :altsvc, stream: 0, max_age: Integer, port: Integer, proto: "String", host: String }
68
+ type altsvc_frame = { type: :altsvc, max_age: Integer, port: Integer, proto: "String", host: String }
58
69
 
59
70
  # # ORIGIN
60
- # type origin_frame = { type: :origin, stream: 0, origin: Array[String] }
71
+ type origin_frame = { type: :origin, origin: Array[String] }
61
72
 
62
73
  # # PING
63
- # type ping_frame = { type: :ping, payload: String, length: Integer }
74
+ type ping_frame = { type: :ping, payload: String, length: Integer }
64
75
 
65
76
  # # GOAWAY
66
- # type goaway_frame = { type: :goaway, stream: 0, last_stream: Integer, error: Symbol? }
77
+ type goaway_frame = { type: :goaway, last_stream: Integer, error: Symbol? }
67
78
 
68
- # type frame = headers_frame | data_frame | push_promise_frame |
79
+ # type frame = common_frame & (headers_frame | data_frame | push_promise_frame |
69
80
  # settings_frame | window_update_frame | priority_frame | altsvc_frame |
70
- # origin_frame | ping_frame | goaway_frame
81
+ # origin_frame | ping_frame | goaway_frame)
71
82
 
72
83
  type frame_key = :type | :flags | :stream | :padding | :ignore |
73
84
  # headers
data/sig/client.rbs CHANGED
@@ -1,5 +1,7 @@
1
1
  module HTTP2
2
2
  class Client < Connection
3
+ @h2c_upgrade: Symbol?
4
+
3
5
  def upgrade: () -> Stream
4
6
 
5
7
  def send_connection_preface: () -> void