http-2 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -2
  3. data/lib/http/2/buffer.rb +6 -4
  4. data/lib/http/2/client.rb +5 -1
  5. data/lib/http/2/compressor.rb +42 -34
  6. data/lib/http/2/connection.rb +72 -86
  7. data/lib/http/2/emitter.rb +4 -1
  8. data/lib/http/2/error.rb +2 -0
  9. data/lib/http/2/flow_buffer.rb +8 -3
  10. data/lib/http/2/framer.rb +83 -94
  11. data/lib/http/2/huffman.rb +19 -17
  12. data/lib/http/2/server.rb +9 -7
  13. data/lib/http/2/stream.rb +48 -48
  14. data/lib/http/2/version.rb +3 -1
  15. data/lib/http/2.rb +2 -0
  16. metadata +7 -60
  17. data/.autotest +0 -20
  18. data/.coveralls.yml +0 -1
  19. data/.gitignore +0 -20
  20. data/.gitmodules +0 -3
  21. data/.rspec +0 -5
  22. data/.rubocop.yml +0 -93
  23. data/.rubocop_todo.yml +0 -131
  24. data/.travis.yml +0 -17
  25. data/Gemfile +0 -16
  26. data/Guardfile +0 -18
  27. data/Guardfile.h2spec +0 -12
  28. data/Rakefile +0 -49
  29. data/example/Gemfile +0 -3
  30. data/example/README.md +0 -44
  31. data/example/client.rb +0 -122
  32. data/example/helper.rb +0 -19
  33. data/example/keys/server.crt +0 -20
  34. data/example/keys/server.key +0 -27
  35. data/example/server.rb +0 -139
  36. data/example/upgrade_client.rb +0 -153
  37. data/example/upgrade_server.rb +0 -203
  38. data/http-2.gemspec +0 -22
  39. data/lib/tasks/generate_huffman_table.rb +0 -166
  40. data/spec/buffer_spec.rb +0 -28
  41. data/spec/client_spec.rb +0 -188
  42. data/spec/compressor_spec.rb +0 -666
  43. data/spec/connection_spec.rb +0 -681
  44. data/spec/emitter_spec.rb +0 -54
  45. data/spec/framer_spec.rb +0 -487
  46. data/spec/h2spec/h2spec.darwin +0 -0
  47. data/spec/h2spec/output/non_secure.txt +0 -317
  48. data/spec/helper.rb +0 -147
  49. data/spec/hpack_test_spec.rb +0 -84
  50. data/spec/huffman_spec.rb +0 -68
  51. data/spec/server_spec.rb +0 -52
  52. data/spec/stream_spec.rb +0 -878
  53. data/spec/support/deep_dup.rb +0 -55
  54. data/spec/support/duplicable.rb +0 -98
@@ -1,203 +0,0 @@
1
- # frozen_string_literals: true
2
-
3
- require_relative 'helper'
4
- require 'http_parser'
5
-
6
- options = { port: 8080 }
7
- OptionParser.new do |opts|
8
- opts.banner = 'Usage: server.rb [options]'
9
-
10
- opts.on('-s', '--secure', 'HTTPS mode') do |v|
11
- options[:secure] = v
12
- end
13
-
14
- opts.on('-p', '--port [Integer]', 'listen port') do |v|
15
- options[:port] = v
16
- end
17
- end.parse!
18
-
19
- puts "Starting server on port #{options[:port]}"
20
- server = TCPServer.new(options[:port])
21
-
22
- if options[:secure]
23
- ctx = OpenSSL::SSL::SSLContext.new
24
- ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
25
- ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
26
- ctx.npn_protocols = [DRAFT]
27
-
28
- server = OpenSSL::SSL::SSLServer.new(server, ctx)
29
- end
30
-
31
- def request_header_hash
32
- Hash.new do |hash, key|
33
- k = key.to_s.downcase
34
- k.tr! '_', '-'
35
- _, value = hash.find { |header_key, _| header_key.downcase == k }
36
- hash[key] = value if value
37
- end
38
- end
39
-
40
- class UpgradeHandler
41
- VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
42
- UPGRADE_RESPONSE = <<RESP.freeze
43
- HTTP/1.1 101 Switching Protocols
44
- Connection: Upgrade
45
- Upgrade: h2c
46
-
47
- RESP
48
-
49
- attr_reader :complete, :headers, :body, :parsing
50
-
51
- def initialize(conn, sock)
52
- @conn, @sock = conn, sock
53
- @complete, @parsing = false, false
54
- @headers = request_header_hash
55
- @body = ''
56
- @parser = ::HTTP::Parser.new(self)
57
- end
58
-
59
- def <<(data)
60
- @parsing ||= true
61
- @parser << data
62
- return unless complete
63
-
64
- @sock.write UPGRADE_RESPONSE
65
-
66
- settings = headers['http2-settings']
67
- request = {
68
- ':scheme' => 'http',
69
- ':method' => @parser.http_method,
70
- ':authority' => headers['Host'],
71
- ':path' => @parser.request_url,
72
- }.merge(headers)
73
-
74
- @conn.upgrade(settings, request, @body)
75
- end
76
-
77
- def complete!
78
- @complete = true
79
- end
80
-
81
- def on_headers_complete(headers)
82
- @headers.merge! headers
83
- end
84
-
85
- def on_body(chunk)
86
- @body << chunk
87
- end
88
-
89
- def on_message_complete
90
- fail unless VALID_UPGRADE_METHODS.include?(@parser.http_method)
91
- @parsing = false
92
- complete!
93
- end
94
- end
95
-
96
- loop do
97
- sock = server.accept
98
- puts 'New TCP connection!'
99
-
100
- conn = HTTP2::Server.new
101
- conn.on(:frame) do |bytes|
102
- # puts "Writing bytes: #{bytes.unpack("H*").first}"
103
- sock.write bytes
104
- end
105
- conn.on(:frame_sent) do |frame|
106
- puts "Sent frame: #{frame.inspect}"
107
- end
108
- conn.on(:frame_received) do |frame|
109
- puts "Received frame: #{frame.inspect}"
110
- end
111
-
112
- conn.on(:stream) do |stream|
113
- log = Logger.new(stream.id)
114
- req = request_header_hash
115
- buffer = ''
116
-
117
- stream.on(:active) { log.info 'client opened new stream' }
118
- stream.on(:close) do
119
- log.info 'stream closed'
120
- end
121
-
122
- stream.on(:headers) do |h|
123
- req.merge! Hash[*h.flatten]
124
- log.info "request headers: #{h}"
125
- end
126
-
127
- stream.on(:data) do |d|
128
- log.info "payload chunk: <<#{d}>>"
129
- buffer << d
130
- end
131
-
132
- stream.on(:half_close) do
133
- log.info 'client closed its end of the stream'
134
-
135
- if req['Upgrade']
136
- log.info "Processing h2c Upgrade request: #{req}"
137
- if req[':method'] != 'OPTIONS' # Don't respond to OPTIONS...
138
- response = 'Hello h2c world!'
139
- stream.headers({
140
- ':status' => '200',
141
- 'content-length' => response.bytesize.to_s,
142
- 'content-type' => 'text/plain',
143
- }, end_stream: false)
144
- stream.data(response)
145
- end
146
- else
147
-
148
- response = nil
149
- if req[':method'] == 'POST'
150
- log.info "Received POST request, payload: #{buffer}"
151
- response = "Hello HTTP 2.0! POST payload: #{buffer}"
152
- else
153
- log.info 'Received GET request'
154
- response = 'Hello HTTP 2.0! GET request'
155
- end
156
-
157
- stream.headers({
158
- ':status' => '200',
159
- 'content-length' => response.bytesize.to_s,
160
- 'content-type' => 'text/plain',
161
- }, end_stream: false)
162
-
163
- # split response into multiple DATA frames
164
- stream.data(response.slice!(0, 5), end_stream: false)
165
- stream.data(response)
166
- end
167
- end
168
- end
169
-
170
- uh = UpgradeHandler.new(conn, sock)
171
-
172
- while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
173
- data = sock.readpartial(1024)
174
- # puts "Received bytes: #{data.unpack("H*").first}"
175
-
176
- begin
177
- case
178
- when !uh.parsing && !uh.complete
179
-
180
- if data.start_with?(*UpgradeHandler::VALID_UPGRADE_METHODS)
181
- uh << data
182
- else
183
- uh.complete!
184
- conn << data
185
- end
186
-
187
- when uh.parsing && !uh.complete
188
- uh << data
189
-
190
- when uh.complete
191
- conn << data
192
- end
193
-
194
- rescue StandardError => e
195
- puts "Exception: #{e}, #{e.message} - closing socket."
196
- puts e.backtrace.last(10).join("\n")
197
- sock.close
198
- end
199
- end
200
- end
201
-
202
- # echo foo=bar | nghttp -d - -t 0 -vu http://127.0.0.1:8080/
203
- # nghttp -vu http://127.0.0.1:8080/
data/http-2.gemspec DELETED
@@ -1,22 +0,0 @@
1
- lib = File.expand_path('./lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'http/2/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'http-2'
7
- spec.version = HTTP2::VERSION
8
- spec.authors = ['Ilya Grigorik', 'Kaoru Maeda']
9
- spec.email = ['ilya@igvita.com']
10
- spec.description = 'Pure-ruby HTTP 2.0 protocol implementation'
11
- spec.summary = spec.description
12
- spec.homepage = 'https://github.com/igrigorik/http-2'
13
- spec.license = 'MIT'
14
- spec.required_ruby_version = '>=2.1.0'
15
-
16
- spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ['lib']
20
-
21
- spec.add_development_dependency 'bundler'
22
- end
@@ -1,166 +0,0 @@
1
- desc 'Generate Huffman precompiled table in huffman_statemachine.rb'
2
- task :generate_table do
3
- HuffmanTable::Node.generate_state_table
4
- end
5
-
6
- require_relative '../http/2/huffman'
7
-
8
- # @private
9
- module HuffmanTable
10
- BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
11
- EOS = 256
12
-
13
- class Node
14
- attr_accessor :next, :emit, :final, :depth
15
- attr_accessor :transitions
16
- attr_accessor :id
17
- @@id = 0 # rubocop:disable Style/ClassVars
18
- def initialize(depth)
19
- @next = [nil, nil]
20
- @id = @@id
21
- @@id += 1 # rubocop:disable Style/ClassVars
22
- @final = false
23
- @depth = depth
24
- end
25
-
26
- def add(code, len, chr)
27
- self.final = true if chr == EOS && @depth <= 7
28
- if len.zero?
29
- @emit = chr
30
- else
31
- bit = (code & (1 << (len - 1))).zero? ? 0 : 1
32
- node = @next[bit] ||= Node.new(@depth + 1)
33
- node.add(code, len - 1, chr)
34
- end
35
- end
36
-
37
- class Transition
38
- attr_accessor :emit, :node
39
- def initialize(emit, node)
40
- @emit = emit
41
- @node = node
42
- end
43
- end
44
-
45
- def self.generate_tree
46
- @root = new(0)
47
- HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
48
- code, len = c
49
- @root.add(code, len, chr)
50
- end
51
- puts "#{@@id} nodes"
52
- @root
53
- end
54
-
55
- def self.generate_machine
56
- generate_tree
57
- togo = Set[@root]
58
- @states = Set[@root]
59
-
60
- until togo.empty?
61
- node = togo.first
62
- togo.delete(node)
63
-
64
- next if node.transitions
65
- node.transitions = Array[1 << BITS_AT_ONCE]
66
-
67
- (1 << BITS_AT_ONCE).times do |input|
68
- n = node
69
- emit = ''
70
- (BITS_AT_ONCE - 1).downto(0) do |i|
71
- bit = (input & (1 << i)).zero? ? 0 : 1
72
- n = n.next[bit]
73
- next unless n.emit
74
- if n.emit == EOS
75
- emit = EOS # cause error on decoding
76
- else
77
- emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
78
- end
79
- n = @root
80
- end
81
- node.transitions[input] = Transition.new(emit, n)
82
- togo << n
83
- @states << n
84
- end
85
- end
86
- puts "#{@states.size} states"
87
- @root
88
- end
89
-
90
- def self.generate_state_table
91
- generate_machine
92
- state_id = {}
93
- id_state = {}
94
- state_id[@root] = 0
95
- id_state[0] = @root
96
- max_final = 0
97
- id = 1
98
- (@states - [@root]).sort_by { |s| s.final ? 0 : 1 }.each do |s|
99
- state_id[s] = id
100
- id_state[id] = s
101
- max_final = id if s.final
102
- id += 1
103
- end
104
-
105
- File.open(File.expand_path('../http/2/huffman_statemachine.rb', File.dirname(__FILE__)), 'w') do |f|
106
- f.print <<HEADER
107
- # Machine generated Huffman decoder state machine.
108
- # DO NOT EDIT THIS FILE.
109
-
110
- # The following task generates this file.
111
- # rake generate_huffman_table
112
-
113
- module HTTP2
114
- module Header
115
- class Huffman
116
- # :nodoc:
117
- MAX_FINAL_STATE = #{max_final}
118
- MACHINE = [
119
- HEADER
120
- id.times do |i|
121
- n = id_state[i]
122
- f.print ' ['
123
- string = (1 << BITS_AT_ONCE).times.map do |t|
124
- transition = n.transitions.fetch(t)
125
- emit = transition.emit
126
- unless emit == EOS
127
- bytes = emit.bytes
128
- fail ArgumentError if bytes.size > 1
129
- emit = bytes.first
130
- end
131
- "[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
132
- end.join(', ')
133
- f.print(string)
134
- f.print "],\n"
135
- end
136
- f.print <<TAILER
137
- ].each { |arr| arr.each { |subarr| subarr.each(&:freeze) }.freeze }.freeze
138
- end
139
- end
140
- end
141
- TAILER
142
- end
143
- end
144
-
145
- class << self
146
- attr_reader :root
147
- end
148
-
149
- # Test decoder
150
- def self.decode(input)
151
- emit = ''
152
- n = root
153
- nibbles = input.unpack('C*').flat_map { |b| [((b & 0xf0) >> 4), b & 0xf] }
154
- until nibbles.empty?
155
- nb = nibbles.shift
156
- t = n.transitions[nb]
157
- emit << t.emit
158
- n = t.node
159
- end
160
- unless n.final && nibbles.all? { |x| x == 0xf }
161
- puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
162
- end
163
- emit
164
- end
165
- end
166
- end
data/spec/buffer_spec.rb DELETED
@@ -1,28 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.describe HTTP2::Buffer do
4
- let(:b) { Buffer.new('émalgré') }
5
-
6
- it 'should force 8-bit encoding' do
7
- expect(b.encoding.to_s).to eq 'ASCII-8BIT'
8
- end
9
-
10
- it 'should force 8-bit encoding when adding data' do
11
- b << 'émalgré'
12
- expect(b.encoding.to_s).to eq 'ASCII-8BIT'
13
- b.prepend('émalgré')
14
- expect(b.encoding.to_s).to eq 'ASCII-8BIT'
15
- end
16
-
17
- it 'should return bytesize of the buffer' do
18
- expect(b.size).to eq 9
19
- end
20
-
21
- it 'should read single byte at a time' do
22
- 9.times { expect(b.read(1)).not_to be_nil }
23
- end
24
-
25
- it 'should unpack an unsigned 32-bit int' do
26
- expect(Buffer.new([256].pack('N')).read_uint32).to eq 256
27
- end
28
- end
data/spec/client_spec.rb DELETED
@@ -1,188 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.describe HTTP2::Client do
4
- include FrameHelpers
5
- before(:each) do
6
- @client = Client.new
7
- end
8
-
9
- let(:f) { Framer.new }
10
-
11
- context 'initialization and settings' do
12
- it 'should return odd stream IDs' do
13
- expect(@client.new_stream.id).not_to be_even
14
- end
15
-
16
- it 'should emit connection header and SETTINGS on new client connection' do
17
- frames = []
18
- @client.on(:frame) { |bytes| frames << bytes }
19
- @client.ping('12345678')
20
-
21
- expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
22
- expect(f.parse(frames[1])[:type]).to eq :settings
23
- end
24
-
25
- it 'should initialize client with custom connection settings' do
26
- frames = []
27
-
28
- @client = Client.new(settings_max_concurrent_streams: 200)
29
- @client.on(:frame) { |bytes| frames << bytes }
30
- @client.ping('12345678')
31
-
32
- frame = f.parse(frames[1])
33
- expect(frame[:type]).to eq :settings
34
- expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
35
- end
36
-
37
- it 'should initialize client when receiving server settings before sending ack' do
38
- frames = []
39
- @client.on(:frame) { |bytes| frames << bytes }
40
- @client << f.generate(settings_frame)
41
-
42
- expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
43
- expect(f.parse(frames[1])[:type]).to eq :settings
44
- ack_frame = f.parse(frames[2])
45
- expect(ack_frame[:type]).to eq :settings
46
- expect(ack_frame[:flags]).to include(:ack)
47
- end
48
- end
49
-
50
- context 'upgrade' do
51
- it 'fails when client has already created streams' do
52
- @client.new_stream
53
- expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
54
- end
55
-
56
- it 'sends the preface' do
57
- expect(@client).to receive(:send_connection_preface)
58
- @client.upgrade
59
- end
60
-
61
- it 'initializes the first stream in the half-closed state' do
62
- stream = @client.upgrade
63
- expect(stream.state).to be(:half_closed_local)
64
- end
65
- end
66
-
67
- context 'push' do
68
- it 'should disallow client initiated push' do
69
- expect do
70
- @client.promise({}) {}
71
- end.to raise_error(NoMethodError)
72
- end
73
-
74
- it 'should raise error on PUSH_PROMISE against stream 0' do
75
- expect do
76
- @client << set_stream_id(f.generate(push_promise_frame), 0)
77
- end.to raise_error(ProtocolError)
78
- end
79
-
80
- it 'should raise error on PUSH_PROMISE against bogus stream' do
81
- expect do
82
- @client << set_stream_id(f.generate(push_promise_frame), 31_415)
83
- end.to raise_error(ProtocolError)
84
- end
85
-
86
- it 'should raise error on PUSH_PROMISE against non-idle stream' do
87
- expect do
88
- s = @client.new_stream
89
- s.send headers_frame
90
-
91
- @client << set_stream_id(f.generate(push_promise_frame), s.id)
92
- @client << set_stream_id(f.generate(push_promise_frame), s.id)
93
- end.to raise_error(ProtocolError)
94
- end
95
-
96
- it 'should emit stream object for received PUSH_PROMISE' do
97
- s = @client.new_stream
98
- s.send headers_frame
99
-
100
- promise = nil
101
- @client.on(:promise) { |stream| promise = stream }
102
- @client << set_stream_id(f.generate(push_promise_frame), s.id)
103
-
104
- expect(promise.id).to eq 2
105
- expect(promise.state).to eq :reserved_remote
106
- end
107
-
108
- it 'should emit promise headers for received PUSH_PROMISE' do
109
- header = nil
110
- s = @client.new_stream
111
- s.send headers_frame
112
-
113
- @client.on(:promise) do |stream|
114
- stream.on(:promise_headers) do |h|
115
- header = h
116
- end
117
- end
118
- @client << set_stream_id(f.generate(push_promise_frame), s.id)
119
-
120
- expect(header).to be_a(Array)
121
- # expect(header).to eq([%w(a b)])
122
- end
123
-
124
- it 'should auto RST_STREAM promises against locally-RST stream' do
125
- s = @client.new_stream
126
- s.send headers_frame
127
- s.close
128
-
129
- allow(@client).to receive(:send)
130
- expect(@client).to receive(:send) do |frame|
131
- expect(frame[:type]).to eq :rst_stream
132
- expect(frame[:stream]).to eq 2
133
- end
134
-
135
- @client << set_stream_id(f.generate(push_promise_frame), s.id)
136
- end
137
- end
138
-
139
- context 'alt-svc' do
140
- context 'received in the connection' do
141
- it 'should emit :altsvc when receiving one' do
142
- @client << f.generate(settings_frame)
143
- frame = nil
144
- @client.on(:altsvc) do |f|
145
- frame = f
146
- end
147
- @client << f.generate(altsvc_frame)
148
- expect(frame).to be_a(Hash)
149
- end
150
- it 'should not emit :altsvc when the frame when contains no host' do
151
- @client << f.generate(settings_frame)
152
- frame = nil
153
- @client.on(:altsvc) do |f|
154
- frame = f
155
- end
156
-
157
- @client << f.generate(altsvc_frame.merge(origin: nil))
158
- expect(frame).to be_nil
159
- end
160
- end
161
- context 'received in a stream' do
162
- it 'should emit :altsvc' do
163
- s = @client.new_stream
164
- s.send headers_frame
165
- s.close
166
-
167
- frame = nil
168
- s.on(:altsvc) { |f| frame = f }
169
-
170
- @client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
171
-
172
- expect(frame).to be_a(Hash)
173
- end
174
- it 'should not emit :alt_svc when the frame when contains a origin' do
175
- s = @client.new_stream
176
- s.send headers_frame
177
- s.close
178
-
179
- frame = nil
180
- s.on(:altsvc) { |f| frame = f }
181
-
182
- @client << set_stream_id(f.generate(altsvc_frame), s.id)
183
-
184
- expect(frame).to be_nil
185
- end
186
- end
187
- end
188
- end