http-2 0.8.4 → 0.9.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
  SHA1:
3
- metadata.gz: df6b5bd32b2217bebfb3205cfab1bd78f2a5456d
4
- data.tar.gz: eb9d683a528819195bf81d4480549b2d8ac7bab6
3
+ metadata.gz: f37bf7f07706136db469b5c188cf13b2995d0c16
4
+ data.tar.gz: e51aeb9218b11e3cada90485ba60b5342e89322f
5
5
  SHA512:
6
- metadata.gz: 686e87ef8beeedd1f3ac1371845b759687231ba62e3bab532d3d15d65febc179bc19f44d4a025708ecc62e2f0e7dd0689cfbbeef19e47c8ee52df9d050066228
7
- data.tar.gz: 41b003cfeb4a127efcf9d4709167db3ff40934a175a08bd19d23a3874d6dd7e615f99ab967c5b85b521e54a4cdbfce33b4087c6ddde7eebd0d0fc11d18a4d551
6
+ metadata.gz: 45ca021e60dfe824fcc7bc6bf61d054ce37096a7f0f41ba7c1a5e45f89ad7848f37f4d6638f28d6a93bf6b41f28e8af66abfac28b4fe60d16556d82c1ec78ae3
7
+ data.tar.gz: c5bddd633904cd5ff9aef237debbdbcc78a6b901dad857cd5aa770e8d13ea28e26c158c902f9b840185b4a8d7dfe7c8e2acd9b380991426f0b5abdbfa4bc2f2f
@@ -37,7 +37,7 @@ Metrics/MethodLength:
37
37
  # Offense count: 1
38
38
  # Configuration parameters: CountKeywordArgs.
39
39
  Metrics/ParameterLists:
40
- Max: 6
40
+ Max: 7
41
41
 
42
42
  # Offense count: 10
43
43
  Metrics/PerceivedComplexity:
@@ -55,6 +55,10 @@ conn.on(:frame_received) do |frame|
55
55
  end
56
56
 
57
57
  conn.on(:promise) do |promise|
58
+ promise.on(:promise_headers) do |h|
59
+ log.info "promise request headers: #{h}"
60
+ end
61
+
58
62
  promise.on(:headers) do |h|
59
63
  log.info "promise headers: #{h}"
60
64
  end
@@ -36,9 +36,7 @@ if options[:secure]
36
36
  DRAFT
37
37
  end
38
38
 
39
- ctx.tmp_ecdh_callback = lambda do |*_args|
40
- OpenSSL::PKey::EC.new 'prime256v1'
41
- end
39
+ ctx.ecdh_curves = 'P-256'
42
40
 
43
41
  server = OpenSSL::SSL::SSLServer.new(server, ctx)
44
42
  end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literals: true
2
+
3
+ require_relative 'helper'
4
+ require 'http_parser'
5
+
6
+ OptionParser.new do |opts|
7
+ opts.banner = 'Usage: upgrade_client.rb [options]'
8
+ end.parse!
9
+
10
+ uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
11
+ sock = TCPSocket.new(uri.host, uri.port)
12
+
13
+ conn = HTTP2::Client.new
14
+
15
+ def request_header_hash
16
+ Hash.new do |hash, key|
17
+ k = key.to_s.downcase
18
+ k.tr! '_', '-'
19
+ _, value = hash.find { |header_key, _| header_key.downcase == k }
20
+ hash[key] = value if value
21
+ end
22
+ end
23
+
24
+ conn.on(:frame) do |bytes|
25
+ sock.print bytes
26
+ sock.flush
27
+ end
28
+ conn.on(:frame_sent) do |frame|
29
+ puts "Sent frame: #{frame.inspect}"
30
+ end
31
+ conn.on(:frame_received) do |frame|
32
+ puts "Received frame: #{frame.inspect}"
33
+ end
34
+
35
+ # upgrader module
36
+ class UpgradeHandler
37
+ UPGRADE_REQUEST = <<-RESP.freeze
38
+ GET %s HTTP/1.1
39
+ Connection: Upgrade, HTTP2-Settings
40
+ HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
41
+ Upgrade: h2c
42
+ Host: %s
43
+ User-Agent: http-2 upgrade
44
+ Accept: */*
45
+
46
+ RESP
47
+
48
+ attr_reader :complete, :parsing
49
+ def initialize(conn, sock)
50
+ @conn = conn
51
+ @sock = sock
52
+ @headers = request_header_hash
53
+ @body = ''.b
54
+ @complete, @parsing = false, false
55
+ @parser = ::HTTP::Parser.new(self)
56
+ end
57
+
58
+ def request(uri)
59
+ host = "#{uri.hostname}#{":#{uri.port}" if uri.port != uri.default_port}"
60
+ req = format(UPGRADE_REQUEST, uri.request_uri, host)
61
+ puts req
62
+ @sock << req
63
+ end
64
+
65
+ def <<(data)
66
+ @parsing ||= true
67
+ @parser << data
68
+ return unless complete
69
+ upgrade
70
+ end
71
+
72
+ def complete!
73
+ @complete = true
74
+ end
75
+
76
+ def on_headers_complete(h)
77
+ @headers.merge!(h)
78
+ puts "received headers: #{h}"
79
+ end
80
+
81
+ def on_body(chunk)
82
+ puts "received chunk: #{chunk}"
83
+ @body << chunk
84
+ end
85
+
86
+ def on_message_complete
87
+ fail 'could not upgrade to h2c' unless @parser.status_code == 101
88
+ @parsing = false
89
+ complete!
90
+ end
91
+
92
+ def upgrade
93
+ stream = @conn.upgrade
94
+ log = Logger.new(stream.id)
95
+
96
+ stream.on(:close) do
97
+ log.info 'stream closed'
98
+ end
99
+
100
+ stream.on(:half_close) do
101
+ log.info 'closing client-end of the stream'
102
+ end
103
+
104
+ stream.on(:headers) do |h|
105
+ log.info "response headers: #{h}"
106
+ end
107
+
108
+ stream.on(:data) do |d|
109
+ log.info "response data chunk: <<#{d}>>"
110
+ end
111
+
112
+ stream.on(:altsvc) do |f|
113
+ log.info "received ALTSVC #{f}"
114
+ end
115
+
116
+ @conn.on(:promise) do |promise|
117
+ promise.on(:headers) do |h|
118
+ log.info "promise headers: #{h}"
119
+ end
120
+
121
+ promise.on(:data) do |d|
122
+ log.info "promise data chunk: <<#{d.size}>>"
123
+ end
124
+ end
125
+
126
+ @conn.on(:altsvc) do |f|
127
+ log.info "received ALTSVC #{f}"
128
+ end
129
+ end
130
+ end
131
+
132
+ uh = UpgradeHandler.new(conn, sock)
133
+ puts 'Sending HTTP/1.1 upgrade request'
134
+ uh.request(uri)
135
+
136
+ while !sock.closed? && !sock.eof?
137
+ data = sock.read_nonblock(1024)
138
+
139
+ begin
140
+ if !uh.parsing && !uh.complete
141
+ uh << data
142
+ elsif uh.parsing && !uh.complete
143
+ uh << data
144
+ elsif uh.complete
145
+ conn << data
146
+ end
147
+
148
+ rescue => e
149
+ puts "#{e.class} exception: #{e.message} - closing socket."
150
+ e.backtrace.each { |l| puts "\t" + l }
151
+ conn.close
152
+ sock.close
153
+ end
154
+ end
@@ -38,6 +38,13 @@ module HTTP2
38
38
  super(frame)
39
39
  end
40
40
 
41
+ # sends the preface and initializes the first stream in half-closed state
42
+ def upgrade
43
+ fail ProtocolError unless @stream_id == 1
44
+ send_connection_preface
45
+ new_stream(state: :half_closed_local)
46
+ end
47
+
41
48
  # Emit the connection preface if not yet
42
49
  def send_connection_preface
43
50
  return unless @state == :waiting_connection_preface
@@ -47,5 +54,10 @@ module HTTP2
47
54
  payload = @local_settings.select { |k, v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
48
55
  settings(payload)
49
56
  end
57
+
58
+ def self.settings_header(**settings)
59
+ frame = Framer.new.generate(type: :settings, stream: 0, payload: settings)
60
+ Base64.urlsafe_encode64(frame[9..-1])
61
+ end
50
62
  end
51
63
  end
@@ -199,8 +199,12 @@ module HTTP2
199
199
  commands = []
200
200
  # Literals commands are marked with :noindex when index is not used
201
201
  noindex = [:static, :never].include?(@options[:index])
202
- headers.each do |h|
203
- cmd = addcmd(h)
202
+ headers.each do |field, value|
203
+ # Literal header names MUST be translated to lowercase before
204
+ # encoding and transmission.
205
+ field = field.downcase
206
+ value = '/' if field == ':path' && value.empty?
207
+ cmd = addcmd(field, value)
204
208
  cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
205
209
  commands << cmd
206
210
  process(cmd)
@@ -220,7 +224,7 @@ module HTTP2
220
224
  #
221
225
  # @param header [Array] +[name, value]+
222
226
  # @return [Hash] command
223
- def addcmd(header)
227
+ def addcmd(*header)
224
228
  exact = nil
225
229
  name_only = nil
226
230
 
@@ -442,10 +446,6 @@ module HTTP2
442
446
  def encode(headers)
443
447
  buffer = Buffer.new
444
448
 
445
- # Literal header names MUST be translated to lowercase before
446
- # encoding and transmission.
447
- headers.map! { |hk, hv| [hk.downcase, hv] }
448
-
449
449
  commands = @cc.encode(headers)
450
450
  commands.each do |cmd|
451
451
  buffer << header(cmd)
@@ -147,6 +147,7 @@ module HTTP2
147
147
  #
148
148
  # @param increment [Integer]
149
149
  def window_update(increment)
150
+ @local_window += increment
150
151
  send(type: :window_update, stream: 0, increment: increment)
151
152
  end
152
153
 
@@ -312,6 +313,10 @@ module HTTP2
312
313
  else
313
314
  if (stream = @streams[frame[:stream]])
314
315
  stream << frame
316
+ if frame[:type] == :data
317
+ update_local_window(frame)
318
+ calculate_window_update(@local_window_limit)
319
+ end
315
320
  else
316
321
  case frame[:type]
317
322
  # The PRIORITY frame can be sent for a stream in the "idle" or
@@ -664,7 +669,6 @@ module HTTP2
664
669
 
665
670
  stream.on(:promise, &method(:promise)) if self.is_a? Server
666
671
  stream.on(:frame, &method(:send))
667
- stream.on(:window_update, &method(:window_update))
668
672
 
669
673
  @streams[id] = stream
670
674
  end
@@ -689,6 +693,7 @@ module HTTP2
689
693
  backtrace = (e && e.backtrace) || []
690
694
  fail Error.const_get(klass), msg, backtrace
691
695
  end
696
+ alias error connection_error
692
697
 
693
698
  def manage_state(_)
694
699
  yield
@@ -13,6 +13,37 @@ module HTTP2
13
13
 
14
14
  private
15
15
 
16
+ def update_local_window(frame)
17
+ frame_size = frame[:payload].bytesize
18
+ frame_size += frame[:padding] || 0
19
+ @local_window -= frame_size
20
+ end
21
+
22
+ def calculate_window_update(window_max_size)
23
+ # If DATA frame is received with length > 0 and
24
+ # current received window size + delta length is strictly larger than
25
+ # local window size, it throws a flow control error.
26
+ #
27
+ error(:flow_control_error) if @local_window < 0
28
+
29
+ # Send WINDOW_UPDATE if the received window size goes over
30
+ # the local window size / 2.
31
+ #
32
+ # The HTTP/2 spec mandates that every DATA frame received
33
+ # generates a WINDOW_UPDATE to send. In some cases however,
34
+ # (ex: DATA frames with short payloads),
35
+ # the noise generated by flow control frames creates enough
36
+ # congestion for this to be deemed very inefficient.
37
+ #
38
+ # This heuristic was inherited from nghttp, which delays the
39
+ # WINDOW_UPDATE until at least half the window is exhausted.
40
+ # This works because the sender doesn't need those increments
41
+ # until the receiver window is exhausted, after which he'll be
42
+ # waiting for the WINDOW_UPDATE frame.
43
+ return unless @local_window <= (window_max_size / 2)
44
+ window_update(window_max_size - @local_window)
45
+ end
46
+
16
47
  # Buffers outgoing DATA frames and applies flow control logic to split
17
48
  # and emit DATA frames based on current flow control window. If the
18
49
  # window is large enough, the data is sent immediately. Otherwise, the
@@ -71,22 +71,24 @@ module HTTP2
71
71
  # @param exclusive [Boolean]
72
72
  # @param window [Integer]
73
73
  # @param parent [Stream]
74
- def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil)
74
+ # @param state [Symbol]
75
+ def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
75
76
  @connection = connection
76
77
  @id = id
77
78
  @weight = weight
78
79
  @dependency = dependency
79
80
  process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
81
+ @local_window_max_size = connection.local_settings[:settings_initial_window_size]
80
82
  @local_window = connection.local_settings[:settings_initial_window_size]
81
83
  @remote_window = connection.remote_settings[:settings_initial_window_size]
82
84
  @parent = parent
83
- @state = :idle
85
+ @state = state
84
86
  @error = false
85
87
  @closed = false
86
88
  @send_buffer = []
87
89
 
88
90
  on(:window) { |v| @remote_window = v }
89
- on(:local_window) { |v| @local_window = v }
91
+ on(:local_window) { |v| @local_window_max_size = @local_window = v }
90
92
  end
91
93
 
92
94
  # Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
@@ -97,16 +99,14 @@ module HTTP2
97
99
 
98
100
  case frame[:type]
99
101
  when :data
100
- window_size = frame[:payload].bytesize
101
- window_size += frame[:padding] || 0
102
- @local_window -= window_size
102
+ update_local_window(frame)
103
+ # Emit DATA frame
103
104
  emit(:data, frame[:payload]) unless frame[:ignore]
104
-
105
- # Automatically send WINDOW_UPDATE,
106
- # assuming that emit(:data) can now receive next data
107
- window_update(window_size) if window_size > 0
108
- when :headers, :push_promise
105
+ calculate_window_update(@local_window_max_size)
106
+ when :headers
109
107
  emit(:headers, frame[:payload]) unless frame[:ignore]
108
+ when :push_promise
109
+ emit(:promise_headers, frame[:payload]) unless frame[:ignore]
110
110
  when :priority
111
111
  process_priority(frame)
112
112
  when :window_update
@@ -153,7 +153,7 @@ module HTTP2
153
153
  flags << :end_headers if end_headers
154
154
  flags << :end_stream if end_stream
155
155
 
156
- send(type: :headers, flags: flags, payload: headers.to_a)
156
+ send(type: :headers, flags: flags, payload: headers)
157
157
  end
158
158
 
159
159
  def promise(headers, end_headers: true, &block)
@@ -228,8 +228,6 @@ module HTTP2
228
228
  #
229
229
  # @param increment [Integer]
230
230
  def window_update(increment)
231
- # always emit connection-level WINDOW_UPDATE
232
- emit(:window_update, increment)
233
231
  # emit stream-level WINDOW_UPDATE unless stream is closed
234
232
  return if @state == :closed || @state == :remote_closed
235
233
  send(type: :window_update, increment: increment)
@@ -602,6 +600,7 @@ module HTTP2
602
600
  klass = error.to_s.split('_').map(&:capitalize).join
603
601
  fail Error.const_get(klass), msg
604
602
  end
603
+ alias error stream_error
605
604
 
606
605
  def manage_state(frame)
607
606
  transition(frame, true)
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.8.4'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
@@ -34,6 +34,23 @@ RSpec.describe HTTP2::Client do
34
34
  end
35
35
  end
36
36
 
37
+ context 'upgrade' do
38
+ it 'fails when client has already created streams' do
39
+ @client.new_stream
40
+ expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
41
+ end
42
+
43
+ it 'sends the preface' do
44
+ expect(@client).to receive(:send_connection_preface)
45
+ @client.upgrade
46
+ end
47
+
48
+ it 'initializes the first stream in the half-closed state' do
49
+ stream = @client.upgrade
50
+ expect(stream.state).to be(:half_closed_local)
51
+ end
52
+ end
53
+
37
54
  context 'push' do
38
55
  it 'should disallow client initiated push' do
39
56
  expect do
@@ -69,12 +86,28 @@ RSpec.describe HTTP2::Client do
69
86
 
70
87
  promise = nil
71
88
  @client.on(:promise) { |stream| promise = stream }
72
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
89
+ @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
73
90
 
74
91
  expect(promise.id).to eq 2
75
92
  expect(promise.state).to eq :reserved_remote
76
93
  end
77
94
 
95
+ it 'should emit promise headers for received PUSH_PROMISE' do
96
+ header = nil
97
+ s = @client.new_stream
98
+ s.send HEADERS.deep_dup
99
+
100
+ @client.on(:promise) do |stream|
101
+ stream.on(:promise_headers) do |h|
102
+ header = h
103
+ end
104
+ end
105
+ @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
106
+
107
+ expect(header).to be_a(Array)
108
+ # expect(header).to eq([%w(a b)])
109
+ end
110
+
78
111
  it 'should auto RST_STREAM promises against locally-RST stream' do
79
112
  s = @client.new_stream
80
113
  s.send HEADERS.deep_dup
@@ -237,6 +237,18 @@ RSpec.describe HTTP2::Header do
237
237
  expect(cc.table.first[0]).to eq 'test2'
238
238
  end
239
239
  end
240
+
241
+ context 'encode' do
242
+ it 'downcases the field' do
243
+ expect(EncodingContext.new.encode([['Content-Length', '5']]))
244
+ .to eq(EncodingContext.new.encode([['content-length', '5']]))
245
+ end
246
+
247
+ it 'fills :path if empty' do
248
+ expect(EncodingContext.new.encode([[':path', '']]))
249
+ .to eq(EncodingContext.new.encode([[':path', '/']]))
250
+ end
251
+ end
240
252
  end
241
253
 
242
254
  spec_examples = [
@@ -237,6 +237,28 @@ RSpec.describe HTTP2::Connection do
237
237
  expect(@conn.buffered_amount).to eq 0
238
238
  expect(@conn.remote_window).to eq 900
239
239
  end
240
+
241
+ it 'should update window when data received is over half of the maximum local window size' do
242
+ settings, data = SETTINGS.dup, DATA.dup
243
+ conn = Client.new(settings_initial_window_size: 500)
244
+
245
+ conn.receive f.generate(settings)
246
+ s1 = conn.new_stream
247
+ s2 = conn.new_stream
248
+
249
+ s1.send HEADERS.deep_dup
250
+ s2.send HEADERS.deep_dup
251
+ expect(conn).to receive(:send) do |frame|
252
+ expect(frame[:type]).to eq :window_update
253
+ expect(frame[:stream]).to eq 0
254
+ expect(frame[:increment]).to eq 400
255
+ end
256
+ conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
257
+ conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
258
+ expect(s1.local_window).to eq 300
259
+ expect(s2.local_window).to eq 300
260
+ expect(conn.local_window).to eq 500
261
+ end
240
262
  end
241
263
 
242
264
  context 'framing' do
@@ -598,19 +598,28 @@ RSpec.describe HTTP2::Stream do
598
598
  expect(s1.remote_window).to eq 900
599
599
  end
600
600
 
601
- it 'should keep track of incoming flow control' do
601
+ it 'should not update window when data received is less than half of maximum local window size' do
602
602
  data = DATA.deep_dup
603
603
  datalen = data[:payload].bytesize
604
- expect(@stream).to receive(:send) do |frame|
604
+ expect(@stream).not_to receive(:send) do |frame|
605
605
  expect(frame[:type]).to eq :window_update
606
606
  expect(frame[:increment]).to eq datalen
607
607
  end
608
- expect(@client).to receive(:send) do |frame|
608
+ @stream.receive HEADERS.deep_dup
609
+ @stream.receive data
610
+ end
611
+
612
+ it 'should update window when data received is over half of the maximum local window size' do
613
+ data1 = DATA.merge(payload: 'a'*16_384, flags: [])
614
+ data2 = DATA.merge(payload: 'a'*16_384)
615
+ datalen = 16_384 * 2
616
+ expect(@stream).to receive(:send) do |frame|
609
617
  expect(frame[:type]).to eq :window_update
610
618
  expect(frame[:increment]).to eq datalen
611
619
  end
612
620
  @stream.receive HEADERS.deep_dup
613
- @stream.receive data
621
+ @stream.receive data1
622
+ @stream.receive data2
614
623
  end
615
624
  end
616
625
 
@@ -642,7 +651,7 @@ RSpec.describe HTTP2::Stream do
642
651
 
643
652
  expect(@stream).to receive(:send) do |frame|
644
653
  expect(frame[:type]).to eq :headers
645
- expect(frame[:payload]).to eq payload.to_a
654
+ expect(frame[:payload]).to eq payload
646
655
  expect(frame[:flags]).to eq [:end_headers]
647
656
  end
648
657
 
@@ -822,8 +831,8 @@ RSpec.describe HTTP2::Stream do
822
831
  expect(order).to eq [:reserved, :active, :half_close, :close]
823
832
  end
824
833
 
825
- it 'client: headers > active > headers > .. > data > close' do
826
- order, headers = [], []
834
+ it 'client: promise_headers > active > headers > .. > data > close' do
835
+ order, headers, promise_headers = [], [], []
827
836
  @client.on(:promise) do |push|
828
837
  order << :reserved
829
838
 
@@ -832,6 +841,10 @@ RSpec.describe HTTP2::Stream do
832
841
  push.on(:half_close) { order << :half_close }
833
842
  push.on(:close) { order << :close }
834
843
 
844
+ push.on(:promise_headers) do |h|
845
+ order << :promise_headers
846
+ promise_headers += h
847
+ end
835
848
  push.on(:headers) do |h|
836
849
  order << :headers
837
850
  headers += h
@@ -845,10 +858,11 @@ RSpec.describe HTTP2::Stream do
845
858
  push.data('somedata')
846
859
  end
847
860
 
848
- expect(headers).to eq([%w(key val), %w(key2 val2)])
861
+ expect(promise_headers).to eq([%w(key val)])
862
+ expect(headers).to eq([%w(key2 val2)])
849
863
  expect(order).to eq [
850
864
  :reserved,
851
- :headers,
865
+ :promise_headers,
852
866
  :active,
853
867
  :headers,
854
868
  :half_close,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http-2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Grigorik
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-05-09 00:00:00.000000000 Z
12
+ date: 2018-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -52,6 +52,7 @@ files:
52
52
  - example/keys/server.crt
53
53
  - example/keys/server.key
54
54
  - example/server.rb
55
+ - example/upgrade_client.rb
55
56
  - example/upgrade_server.rb
56
57
  - http-2.gemspec
57
58
  - lib/http/2.rb