mieps_http-2 0.8.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 +7 -0
- data/.autotest +20 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +13 -0
- data/Gemfile +18 -0
- data/README.md +285 -0
- data/Rakefile +11 -0
- data/example/README.md +40 -0
- data/example/client.rb +117 -0
- data/example/helper.rb +19 -0
- data/example/keys/mycert.pem +23 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +97 -0
- data/example/upgrade_server.rb +193 -0
- data/http-2.gemspec +23 -0
- data/lib/http/2/buffer.rb +34 -0
- data/lib/http/2/client.rb +51 -0
- data/lib/http/2/compressor.rb +557 -0
- data/lib/http/2/connection.rb +654 -0
- data/lib/http/2/emitter.rb +45 -0
- data/lib/http/2/error.rb +44 -0
- data/lib/http/2/flow_buffer.rb +67 -0
- data/lib/http/2/framer.rb +440 -0
- data/lib/http/2/huffman.rb +323 -0
- data/lib/http/2/huffman_statemachine.rb +272 -0
- data/lib/http/2/server.rb +132 -0
- data/lib/http/2/stream.rb +576 -0
- data/lib/http/2/version.rb +3 -0
- data/lib/http/2.rb +13 -0
- data/lib/tasks/generate_huffman_table.rb +166 -0
- data/spec/buffer_spec.rb +21 -0
- data/spec/client_spec.rb +92 -0
- data/spec/compressor_spec.rb +535 -0
- data/spec/connection_spec.rb +581 -0
- data/spec/emitter_spec.rb +54 -0
- data/spec/framer_spec.rb +487 -0
- data/spec/helper.rb +128 -0
- data/spec/hpack_test_spec.rb +79 -0
- data/spec/huffman_spec.rb +68 -0
- data/spec/server_spec.rb +51 -0
- data/spec/stream_spec.rb +794 -0
- metadata +116 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEAzG/uarM0xVUWgGOo5JTLZw0z1zrqBOBUuv2yygtTRMLhuJjU
|
3
|
+
H8qgPsb0i97o/B92mKmAZj6248d8TMEIIcPt7ERHU0uelZq6jovCHfA6O6jkSvqS
|
4
|
+
WAEYW4djHyMnaMHAIW9NwbbMlF51T89vLoVrbtfppzB9oC1F4blg4ezxhGaDgSta
|
5
|
+
p9G5ygzfOPy+8xae6yEyFhDGjURUsnYS+9scSSKnhqmmuj7ZCXQpjhdPcSgxNEcg
|
6
|
+
JuiUr+5Bih7Sa6wqfAIY+DY0vp7bu6+Ljsbsasilx/cc3gdkyg7Mny2LsggSQXR9
|
7
|
+
VOBARWSW4id+d53r1YLDF5wxBmzmx0G7ATcqHQIDAQABAoIBACybj85AZBdaxZom
|
8
|
+
JMgbn3ZQ7yrbdAy0Vkim6sgjSHwMeewpjL+TGvwXtWx/qx64Tsxoz9d/f7Cb6odk
|
9
|
+
5z1W3ydajqWiLmw+Ys6PuD+IF2zFIWsq2ZvSQVpXZE17AjJddGrXOoQ2OtV09uv/
|
10
|
+
OydPfW2mNxl//ylgN4tVQ8qIRPq6b1GWWZvjTw4K3jPrlAifobYBBR+BSk446O7F
|
11
|
+
iGvax5lNNCDMN2y+6hlnhlTHuvc0DXQA0XBhWTNYu8BNNrvC3I31RmxdY7Frm7IA
|
12
|
+
RUGy/l2kLHCRCTF8Q0C4ydpE5ZFgpxkWK7p3QEv/gnVAwsOSN/nThdoorWWHTbNl
|
13
|
+
pA5l1RECgYEA/ASaS9mqWWthUkOW51L6c7IIiRPAhrbPnx1mkAtUPeehHn1+G8Qu
|
14
|
+
upUEXslWokhmQ3UAGhrId6FVYsfftNPMNck9mv4ntW7MoZLXZqTiFSqx4pQTjoYg
|
15
|
+
PQ4c/jrQLsmislcKTiVx6kFYFcnI1ayXXEtaby0lri8XsAR5F90OpycCgYEAz6re
|
16
|
+
DR5EZZKx61wyEfuPWE6aACWlEqlbTa8nTMddxnZUZXtzbwRFapGfs+uHCURF0dGj
|
17
|
+
37cl4q8JdGbbYePk9nlOC4RoSw8Zh3fB4yRSZocB4yB047ofpBmt4BigGtgZ5BLZ
|
18
|
+
zqVREgBUI+tFPPHkMmBY4lCaUsCe11SEwyZFzxsCgYEA3nRNonBy/tVbJZtFw9Eq
|
19
|
+
BB/9isolooQRxrjUBIgLh01Dmj9ZprbILKhHIEgGsd7IbfkD6wcDNx3w2e3mGJ7v
|
20
|
+
3fZR69M2R9+Sv3h3rEIU0mxKct8UWDUqldo0W3CcvP/9HgDYttw0rnuZfjoMjhf3
|
21
|
+
z18wZ3xpi1RES3nXTeox+fcCgYBlPxkjrC4Ml4jHBxwiSFOK6keK6s+gWZF6Pnsa
|
22
|
+
o9jEecyL7bRJ2/s8CeOjBKHBkte3hE4xNEn0SwKBDeTHxSRMRrgWRWfTsHjx4yFU
|
23
|
+
bND/y7LP2XMj1Aq5JwvuxhLJA7Mbz1UBuvfbnu1m1b3cCNMI/JBZRpL25ZKLyVkx
|
24
|
+
C+fdIQKBgA+tLeF10zqGGc4269b6nQWplc5E/qnIRK0cfnKb9BtffmA4FbjUpZKj
|
25
|
+
+cGmbtbw7ySkAIKLp4HoJmzkXJageGTSEb/sQIodxMiJCGvvgJmPPnGzU8OiUGAl
|
26
|
+
VmRjuAQ2eCcsUyvrJYgKW9UWskqSe6z5w/Uxo/sZdHlaGljNdKcn
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/example/server.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
options = { port: 8080 }
|
4
|
+
OptionParser.new do |opts|
|
5
|
+
opts.banner = 'Usage: server.rb [options]'
|
6
|
+
|
7
|
+
opts.on('-s', '--secure', 'HTTPS mode') do |v|
|
8
|
+
options[:secure] = v
|
9
|
+
end
|
10
|
+
|
11
|
+
opts.on('-p', '--port [Integer]', 'listen port') do |v|
|
12
|
+
options[:port] = v
|
13
|
+
end
|
14
|
+
end.parse!
|
15
|
+
|
16
|
+
puts "Starting server on port #{options[:port]}"
|
17
|
+
server = TCPServer.new(options[:port])
|
18
|
+
|
19
|
+
if options[:secure]
|
20
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
21
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/mycert.pem'))
|
22
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/mykey.pem'))
|
23
|
+
ctx.npn_protocols = [DRAFT]
|
24
|
+
|
25
|
+
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
26
|
+
end
|
27
|
+
|
28
|
+
loop do
|
29
|
+
sock = server.accept
|
30
|
+
puts 'New TCP connection!'
|
31
|
+
|
32
|
+
conn = HTTP2::Server.new
|
33
|
+
conn.on(:frame) do |bytes|
|
34
|
+
# puts "Writing bytes: #{bytes.unpack("H*").first}"
|
35
|
+
sock.write bytes
|
36
|
+
end
|
37
|
+
conn.on(:frame_sent) do |frame|
|
38
|
+
puts "Sent frame: #{frame.inspect}"
|
39
|
+
end
|
40
|
+
conn.on(:frame_received) do |frame|
|
41
|
+
puts "Received frame: #{frame.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
conn.on(:stream) do |stream|
|
45
|
+
log = Logger.new(stream.id)
|
46
|
+
req, buffer = {}, ''
|
47
|
+
|
48
|
+
stream.on(:active) { log.info 'cliend opened new stream' }
|
49
|
+
stream.on(:close) { log.info 'stream closed' }
|
50
|
+
|
51
|
+
stream.on(:headers) do |h|
|
52
|
+
req = Hash[*h.flatten]
|
53
|
+
log.info "request headers: #{h}"
|
54
|
+
end
|
55
|
+
|
56
|
+
stream.on(:data) do |d|
|
57
|
+
log.info "payload chunk: <<#{d}>>"
|
58
|
+
buffer << d
|
59
|
+
end
|
60
|
+
|
61
|
+
stream.on(:half_close) do
|
62
|
+
log.info 'client closed its end of the stream'
|
63
|
+
|
64
|
+
response = nil
|
65
|
+
if req[':method'] == 'POST'
|
66
|
+
log.info "Received POST request, payload: #{buffer}"
|
67
|
+
response = "Hello HTTP 2.0! POST payload: #{buffer}"
|
68
|
+
else
|
69
|
+
log.info 'Received GET request'
|
70
|
+
response = 'Hello HTTP 2.0! GET request'
|
71
|
+
end
|
72
|
+
|
73
|
+
stream.headers({
|
74
|
+
':status' => '200',
|
75
|
+
'content-length' => response.bytesize.to_s,
|
76
|
+
'content-type' => 'text/plain',
|
77
|
+
}, end_stream: false)
|
78
|
+
|
79
|
+
# split response into multiple DATA frames
|
80
|
+
stream.data(response.slice!(0, 5), end_stream: false)
|
81
|
+
stream.data(response)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
|
86
|
+
data = sock.readpartial(1024)
|
87
|
+
# puts "Received bytes: #{data.unpack("H*").first}"
|
88
|
+
|
89
|
+
begin
|
90
|
+
conn << data
|
91
|
+
rescue => e
|
92
|
+
puts "#{e.class} exception: #{e.message} - closing socket."
|
93
|
+
e.backtrace.each { |l| puts "\t" + l }
|
94
|
+
sock.close
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'http_parser'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
options = { port: 8080 }
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: server.rb [options]'
|
8
|
+
|
9
|
+
opts.on('-s', '--secure', 'HTTPS mode') do |v|
|
10
|
+
options[:secure] = v
|
11
|
+
end
|
12
|
+
|
13
|
+
opts.on('-p', '--port [Integer]', 'listen port') do |v|
|
14
|
+
options[:port] = v
|
15
|
+
end
|
16
|
+
end.parse!
|
17
|
+
|
18
|
+
puts "Starting server on port #{options[:port]}"
|
19
|
+
server = TCPServer.new(options[:port])
|
20
|
+
|
21
|
+
if options[:secure]
|
22
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
23
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/mycert.pem'))
|
24
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/mykey.pem'))
|
25
|
+
ctx.npn_protocols = [DRAFT]
|
26
|
+
|
27
|
+
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
28
|
+
end
|
29
|
+
|
30
|
+
class UpgradeHandler
|
31
|
+
VALID_UPGRADE_METHODS = %w(GET OPTIONS)
|
32
|
+
UPGRADE_RESPONSE = <<-RESP
|
33
|
+
HTTP/1.1 101 Switching Protocols
|
34
|
+
Connection: Upgrade
|
35
|
+
Upgrade: h2c
|
36
|
+
|
37
|
+
RESP
|
38
|
+
|
39
|
+
attr_reader :complete, :headers, :body, :parsing
|
40
|
+
|
41
|
+
def initialize(conn, sock)
|
42
|
+
@conn, @sock = conn, sock
|
43
|
+
@complete, @parsing = false, false
|
44
|
+
@body = ''
|
45
|
+
@parser = ::HTTP::Parser.new(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def <<(data)
|
49
|
+
@parsing ||= true
|
50
|
+
@parser << data
|
51
|
+
return unless complete
|
52
|
+
|
53
|
+
@sock.write UPGRADE_RESPONSE
|
54
|
+
|
55
|
+
settings = headers['http2-settings']
|
56
|
+
request = {
|
57
|
+
':scheme' => 'http',
|
58
|
+
':method' => @parser.http_method,
|
59
|
+
':authority' => headers['Host'],
|
60
|
+
':path' => @parser.request_url,
|
61
|
+
}.merge(headers)
|
62
|
+
|
63
|
+
@conn.upgrade(settings, request, @body)
|
64
|
+
end
|
65
|
+
|
66
|
+
def complete!
|
67
|
+
@complete = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_headers_complete(headers)
|
71
|
+
@headers = headers
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_body(chunk)
|
75
|
+
@body << chunk
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_message_complete
|
79
|
+
fail unless VALID_UPGRADE_METHODS.include?(@parser.http_method)
|
80
|
+
@parsing = false
|
81
|
+
complete!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
loop do
|
86
|
+
sock = server.accept
|
87
|
+
puts 'New TCP connection!'
|
88
|
+
|
89
|
+
conn = HTTP2::Server.new
|
90
|
+
conn.on(:frame) do |bytes|
|
91
|
+
# puts "Writing bytes: #{bytes.unpack("H*").first}"
|
92
|
+
sock.write bytes
|
93
|
+
end
|
94
|
+
conn.on(:frame_sent) do |frame|
|
95
|
+
puts "Sent frame: #{frame.inspect}"
|
96
|
+
end
|
97
|
+
conn.on(:frame_received) do |frame|
|
98
|
+
puts "Received frame: #{frame.inspect}"
|
99
|
+
end
|
100
|
+
|
101
|
+
conn.on(:stream) do |stream|
|
102
|
+
log = Logger.new(stream.id)
|
103
|
+
req, buffer = {}, ''
|
104
|
+
|
105
|
+
stream.on(:active) { log.info 'client opened new stream' }
|
106
|
+
stream.on(:close) do
|
107
|
+
log.info 'stream closed'
|
108
|
+
end
|
109
|
+
|
110
|
+
stream.on(:headers) do |h|
|
111
|
+
req = Hash[*h.flatten]
|
112
|
+
log.info "request headers: #{h}"
|
113
|
+
end
|
114
|
+
|
115
|
+
stream.on(:data) do |d|
|
116
|
+
log.info "payload chunk: <<#{d}>>"
|
117
|
+
buffer << d
|
118
|
+
end
|
119
|
+
|
120
|
+
stream.on(:half_close) do
|
121
|
+
log.info 'client closed its end of the stream'
|
122
|
+
|
123
|
+
if req['Upgrade']
|
124
|
+
log.info "Processing h2c Upgrade request: #{req}"
|
125
|
+
|
126
|
+
# Don't respond to OPTIONS...
|
127
|
+
if req[':method'] != 'OPTIONS'
|
128
|
+
response = 'Hello h2c world!'
|
129
|
+
stream.headers({
|
130
|
+
':status' => '200',
|
131
|
+
'content-length' => response.bytesize.to_s,
|
132
|
+
'content-type' => 'text/plain',
|
133
|
+
}, end_stream: false)
|
134
|
+
stream.data(response)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
|
138
|
+
response = nil
|
139
|
+
if req[':method'] == 'POST'
|
140
|
+
log.info "Received POST request, payload: #{buffer}"
|
141
|
+
response = "Hello HTTP 2.0! POST payload: #{buffer}"
|
142
|
+
else
|
143
|
+
log.info 'Received GET request'
|
144
|
+
response = 'Hello HTTP 2.0! GET request'
|
145
|
+
end
|
146
|
+
|
147
|
+
stream.headers({
|
148
|
+
':status' => '200',
|
149
|
+
'content-length' => response.bytesize.to_s,
|
150
|
+
'content-type' => 'text/plain',
|
151
|
+
}, end_stream: false)
|
152
|
+
|
153
|
+
# split response into multiple DATA frames
|
154
|
+
stream.data(response.slice!(0, 5), end_stream: false)
|
155
|
+
stream.data(response)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
uh = UpgradeHandler.new(conn, sock)
|
161
|
+
|
162
|
+
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
|
163
|
+
data = sock.readpartial(1024)
|
164
|
+
# puts "Received bytes: #{data.unpack("H*").first}"
|
165
|
+
|
166
|
+
begin
|
167
|
+
case
|
168
|
+
when !uh.parsing && !uh.complete
|
169
|
+
|
170
|
+
if data.start_with?(*UpgradeHandler::VALID_UPGRADE_METHODS)
|
171
|
+
uh << data
|
172
|
+
else
|
173
|
+
uh.complete!
|
174
|
+
conn << data
|
175
|
+
end
|
176
|
+
|
177
|
+
when uh.parsing && !uh.complete
|
178
|
+
uh << data
|
179
|
+
|
180
|
+
when uh.complete
|
181
|
+
conn << data
|
182
|
+
end
|
183
|
+
|
184
|
+
rescue => e
|
185
|
+
puts "Exception: #{e}, #{e.message} - closing socket."
|
186
|
+
puts e.backtrace.last(10).join("\n")
|
187
|
+
sock.close
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# echo foo=bar | nghttp -d - -t 0 -vu http://127.0.0.1:8080/
|
193
|
+
# nghttp -vu http://127.0.0.1:8080/
|
data/http-2.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'http/2/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'mieps_http-2'
|
8
|
+
spec.version = HTTP2::VERSION
|
9
|
+
spec.authors = ['Ilya Grigorik', 'Kaoru Maeda']
|
10
|
+
spec.email = ['ilya@igvita.com']
|
11
|
+
spec.description = 'Fork of Pure-ruby HTTP 2.0 protocol implementation - https://github.com/igrigorik/http-2'
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>=2.1.0'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module HTTP2
|
2
|
+
# Simple binary buffer backed by string.
|
3
|
+
#
|
4
|
+
class Buffer < String
|
5
|
+
UINT32 = 'N'.freeze
|
6
|
+
private_constant :UINT32
|
7
|
+
|
8
|
+
# Forces binary encoding on the string
|
9
|
+
def initialize(*)
|
10
|
+
super.force_encoding(Encoding::BINARY)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Emulate StringIO#read: slice first n bytes from the buffer.
|
14
|
+
#
|
15
|
+
# @param n [Integer] number of bytes to slice from the buffer
|
16
|
+
def read(n)
|
17
|
+
Buffer.new(slice!(0, n))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Alias getbyte to readbyte
|
21
|
+
alias_method :readbyte, :getbyte
|
22
|
+
|
23
|
+
# Emulate StringIO#getbyte: slice first byte from buffer.
|
24
|
+
def getbyte
|
25
|
+
read(1).ord
|
26
|
+
end
|
27
|
+
|
28
|
+
# Slice unsigned 32-bit integer from buffer.
|
29
|
+
# @return [Integer]
|
30
|
+
def read_uint32
|
31
|
+
read(4).unpack(UINT32).first
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module HTTP2
|
2
|
+
# HTTP 2.0 client connection class that implements appropriate header
|
3
|
+
# compression / decompression algorithms and stream management logic.
|
4
|
+
#
|
5
|
+
# Your code is responsible for driving the client object, which in turn
|
6
|
+
# performs all of the necessary HTTP 2.0 encoding / decoding, state
|
7
|
+
# management, and the rest. A simple example:
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# socket = YourTransport.new
|
11
|
+
#
|
12
|
+
# conn = HTTP2::Client.new
|
13
|
+
# conn.on(:frame) {|bytes| socket << bytes }
|
14
|
+
#
|
15
|
+
# while bytes = socket.read
|
16
|
+
# conn << bytes
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class Client < Connection
|
20
|
+
# Initialize new HTTP 2.0 client object.
|
21
|
+
def initialize(**settings)
|
22
|
+
@stream_id = 1
|
23
|
+
@state = :waiting_connection_preface
|
24
|
+
|
25
|
+
@local_role = :client
|
26
|
+
@remote_role = :server
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send an outgoing frame. Connection and stream flow control is managed
|
32
|
+
# by Connection class.
|
33
|
+
#
|
34
|
+
# @see Connection
|
35
|
+
# @param frame [Hash]
|
36
|
+
def send(frame)
|
37
|
+
send_connection_preface
|
38
|
+
super(frame)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Emit the connection preface if not yet
|
42
|
+
def send_connection_preface
|
43
|
+
return unless @state == :waiting_connection_preface
|
44
|
+
@state = :connected
|
45
|
+
emit(:frame, CONNECTION_PREFACE_MAGIC)
|
46
|
+
|
47
|
+
payload = @local_settings.select { |k, v| v != SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
48
|
+
settings(payload)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|