http-2 0.11.0 → 0.12.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 +4 -4
- data/README.md +0 -2
- data/lib/http/2/buffer.rb +6 -4
- data/lib/http/2/client.rb +5 -1
- data/lib/http/2/compressor.rb +42 -34
- data/lib/http/2/connection.rb +72 -86
- data/lib/http/2/emitter.rb +4 -1
- data/lib/http/2/error.rb +2 -0
- data/lib/http/2/flow_buffer.rb +8 -3
- data/lib/http/2/framer.rb +83 -94
- data/lib/http/2/huffman.rb +19 -17
- data/lib/http/2/server.rb +9 -7
- data/lib/http/2/stream.rb +48 -48
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +2 -0
- metadata +7 -60
- data/.autotest +0 -20
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -131
- data/.travis.yml +0 -17
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- data/Rakefile +0 -49
- data/example/Gemfile +0 -3
- data/example/README.md +0 -44
- data/example/client.rb +0 -122
- data/example/helper.rb +0 -19
- data/example/keys/server.crt +0 -20
- data/example/keys/server.key +0 -27
- data/example/server.rb +0 -139
- data/example/upgrade_client.rb +0 -153
- data/example/upgrade_server.rb +0 -203
- data/http-2.gemspec +0 -22
- data/lib/tasks/generate_huffman_table.rb +0 -166
- data/spec/buffer_spec.rb +0 -28
- data/spec/client_spec.rb +0 -188
- data/spec/compressor_spec.rb +0 -666
- data/spec/connection_spec.rb +0 -681
- data/spec/emitter_spec.rb +0 -54
- data/spec/framer_spec.rb +0 -487
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +0 -317
- data/spec/helper.rb +0 -147
- data/spec/hpack_test_spec.rb +0 -84
- data/spec/huffman_spec.rb +0 -68
- data/spec/server_spec.rb +0 -52
- data/spec/stream_spec.rb +0 -878
- data/spec/support/deep_dup.rb +0 -55
- data/spec/support/duplicable.rb +0 -98
data/Rakefile
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'bundler/gem_tasks'
|
2
|
-
require 'rspec/core/rake_task'
|
3
|
-
require 'rubocop/rake_task'
|
4
|
-
require 'yard'
|
5
|
-
require 'open3'
|
6
|
-
require_relative 'lib/tasks/generate_huffman_table'
|
7
|
-
|
8
|
-
RSpec::Core::RakeTask.new(:spec) do |t|
|
9
|
-
t.exclude_pattern = './spec/hpack_test_spec.rb'
|
10
|
-
end
|
11
|
-
|
12
|
-
RSpec::Core::RakeTask.new(:hpack) do |t|
|
13
|
-
t.pattern = './spec/hpack_test_spec.rb'
|
14
|
-
end
|
15
|
-
|
16
|
-
task :h2spec do
|
17
|
-
if /darwin/ !~ RUBY_PLATFORM
|
18
|
-
abort "h2spec rake task currently only works on OSX.
|
19
|
-
Download other binaries from https://github.com/summerwind/h2spec/releases"
|
20
|
-
end
|
21
|
-
|
22
|
-
system 'ruby example/server.rb -p 9000 &', out: File::NULL
|
23
|
-
sleep 1
|
24
|
-
|
25
|
-
output = ''
|
26
|
-
Open3.popen2e('spec/h2spec/h2spec.darwin -p 9000 -o 1') do |_i, oe, _t|
|
27
|
-
oe.each do |l|
|
28
|
-
l.gsub!(/\e\[(\d+)(;\d+)*m/, '')
|
29
|
-
|
30
|
-
output << l
|
31
|
-
if l =~ /passed.*failed/
|
32
|
-
puts "\n#{l}"
|
33
|
-
break # suppress post-summary failure output
|
34
|
-
else
|
35
|
-
print '.'
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
File.write 'spec/h2spec/output/non_secure.txt', output
|
41
|
-
|
42
|
-
system 'kill `pgrep -f example/server.rb`'
|
43
|
-
end
|
44
|
-
|
45
|
-
RuboCop::RakeTask.new
|
46
|
-
YARD::Rake::YardocTask.new
|
47
|
-
|
48
|
-
task default: [:spec, :rubocop]
|
49
|
-
task all: [:default, :hpack]
|
data/example/Gemfile
DELETED
data/example/README.md
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
## Interop
|
2
|
-
|
3
|
-
First, a quick test to ensure that we can talk to ourselves:
|
4
|
-
|
5
|
-
```bash
|
6
|
-
# Direct connection
|
7
|
-
$> ruby server.rb
|
8
|
-
$> ruby client.rb http://localhost:8080/ # GET
|
9
|
-
$> ruby client.rb http://localhost:8080/ -d 'some data' # POST
|
10
|
-
|
11
|
-
# Server push
|
12
|
-
$> ruby server.rb --push
|
13
|
-
$> ruby client.rb http://localhost:8080/ # GET
|
14
|
-
|
15
|
-
# TLS + NPN negotiation
|
16
|
-
$> ruby server.rb --secure
|
17
|
-
$> ruby client.rb https://localhost:8080/ # GET
|
18
|
-
$> ...
|
19
|
-
```
|
20
|
-
|
21
|
-
### [nghttp2](https://github.com/tatsuhiro-t/nghttp2) (HTTP/2.0 C Library)
|
22
|
-
|
23
|
-
Public test server: http://106.186.112.116 (Upgrade + Direct)
|
24
|
-
|
25
|
-
```bash
|
26
|
-
# Direct request (http-2 > nghttp2)
|
27
|
-
$> ruby client.rb http://106.186.112.116/
|
28
|
-
|
29
|
-
# TLS + NPN request (http-2 > nghttp2)
|
30
|
-
$> ruby client.rb https://106.186.112.116/
|
31
|
-
|
32
|
-
# Direct request (nghttp2 > http-2)
|
33
|
-
$> ruby server.rb
|
34
|
-
$> nghttp -vnu http://localhost:8080 # Direct request to Ruby server
|
35
|
-
```
|
36
|
-
|
37
|
-
### Twitter (Java server)
|
38
|
-
|
39
|
-
```bash
|
40
|
-
# NPN + GET request (http-2 > twitter)
|
41
|
-
$> ruby client.rb https://twitter.com/
|
42
|
-
```
|
43
|
-
|
44
|
-
For a complete list of current implementations, see [http2 wiki](https://github.com/http2/http2-spec/wiki/Implementations).
|
data/example/client.rb
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
require_relative 'helper'
|
2
|
-
|
3
|
-
options = {}
|
4
|
-
OptionParser.new do |opts|
|
5
|
-
opts.banner = 'Usage: client.rb [options]'
|
6
|
-
|
7
|
-
opts.on('-d', '--data [String]', 'HTTP payload') do |v|
|
8
|
-
options[:payload] = v
|
9
|
-
end
|
10
|
-
end.parse!
|
11
|
-
|
12
|
-
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
|
13
|
-
tcp = TCPSocket.new(uri.host, uri.port)
|
14
|
-
sock = nil
|
15
|
-
|
16
|
-
if uri.scheme == 'https'
|
17
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
18
|
-
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
19
|
-
|
20
|
-
# For ALPN support, Ruby >= 2.3 and OpenSSL >= 1.0.2 are required
|
21
|
-
|
22
|
-
ctx.alpn_protocols = [DRAFT]
|
23
|
-
ctx.alpn_select_cb = lambda do |protocols|
|
24
|
-
puts "ALPN protocols supported by server: #{protocols}"
|
25
|
-
DRAFT if protocols.include? DRAFT
|
26
|
-
end
|
27
|
-
|
28
|
-
sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
29
|
-
sock.sync_close = true
|
30
|
-
sock.hostname = uri.hostname
|
31
|
-
sock.connect
|
32
|
-
|
33
|
-
if sock.alpn_protocol != DRAFT
|
34
|
-
puts "Failed to negotiate #{DRAFT} via ALPN"
|
35
|
-
exit
|
36
|
-
end
|
37
|
-
else
|
38
|
-
sock = tcp
|
39
|
-
end
|
40
|
-
|
41
|
-
conn = HTTP2::Client.new
|
42
|
-
stream = conn.new_stream
|
43
|
-
log = Logger.new(stream.id)
|
44
|
-
|
45
|
-
conn.on(:frame) do |bytes|
|
46
|
-
# puts "Sending bytes: #{bytes.unpack("H*").first}"
|
47
|
-
sock.print bytes
|
48
|
-
sock.flush
|
49
|
-
end
|
50
|
-
conn.on(:frame_sent) do |frame|
|
51
|
-
puts "Sent frame: #{frame.inspect}"
|
52
|
-
end
|
53
|
-
conn.on(:frame_received) do |frame|
|
54
|
-
puts "Received frame: #{frame.inspect}"
|
55
|
-
end
|
56
|
-
|
57
|
-
conn.on(:promise) do |promise|
|
58
|
-
promise.on(:promise_headers) do |h|
|
59
|
-
log.info "promise request headers: #{h}"
|
60
|
-
end
|
61
|
-
|
62
|
-
promise.on(:headers) do |h|
|
63
|
-
log.info "promise headers: #{h}"
|
64
|
-
end
|
65
|
-
|
66
|
-
promise.on(:data) do |d|
|
67
|
-
log.info "promise data chunk: <<#{d.size}>>"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
conn.on(:altsvc) do |f|
|
72
|
-
log.info "received ALTSVC #{f}"
|
73
|
-
end
|
74
|
-
|
75
|
-
stream.on(:close) do
|
76
|
-
log.info 'stream closed'
|
77
|
-
end
|
78
|
-
|
79
|
-
stream.on(:half_close) do
|
80
|
-
log.info 'closing client-end of the stream'
|
81
|
-
end
|
82
|
-
|
83
|
-
stream.on(:headers) do |h|
|
84
|
-
log.info "response headers: #{h}"
|
85
|
-
end
|
86
|
-
|
87
|
-
stream.on(:data) do |d|
|
88
|
-
log.info "response data chunk: <<#{d}>>"
|
89
|
-
end
|
90
|
-
|
91
|
-
stream.on(:altsvc) do |f|
|
92
|
-
log.info "received ALTSVC #{f}"
|
93
|
-
end
|
94
|
-
|
95
|
-
head = {
|
96
|
-
':scheme' => uri.scheme,
|
97
|
-
':method' => (options[:payload].nil? ? 'GET' : 'POST'),
|
98
|
-
':authority' => [uri.host, uri.port].join(':'),
|
99
|
-
':path' => uri.path,
|
100
|
-
'accept' => '*/*',
|
101
|
-
}
|
102
|
-
|
103
|
-
puts 'Sending HTTP 2.0 request'
|
104
|
-
if head[':method'] == 'GET'
|
105
|
-
stream.headers(head, end_stream: true)
|
106
|
-
else
|
107
|
-
stream.headers(head, end_stream: false)
|
108
|
-
stream.data(options[:payload])
|
109
|
-
end
|
110
|
-
|
111
|
-
while !sock.closed? && !sock.eof?
|
112
|
-
data = sock.read_nonblock(1024)
|
113
|
-
# puts "Received bytes: #{data.unpack("H*").first}"
|
114
|
-
|
115
|
-
begin
|
116
|
-
conn << data
|
117
|
-
rescue StandardError => e
|
118
|
-
puts "#{e.class} exception: #{e.message} - closing socket."
|
119
|
-
e.backtrace.each { |l| puts "\t" + l }
|
120
|
-
sock.close
|
121
|
-
end
|
122
|
-
end
|
data/example/helper.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
$LOAD_PATH << 'lib' << '../lib'
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
require 'socket'
|
5
|
-
require 'openssl'
|
6
|
-
require 'http/2'
|
7
|
-
require 'uri'
|
8
|
-
|
9
|
-
DRAFT = 'h2'.freeze
|
10
|
-
|
11
|
-
class Logger
|
12
|
-
def initialize(id)
|
13
|
-
@id = id
|
14
|
-
end
|
15
|
-
|
16
|
-
def info(msg)
|
17
|
-
puts "[Stream #{@id}]: #{msg}"
|
18
|
-
end
|
19
|
-
end
|
data/example/keys/server.crt
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
-----BEGIN CERTIFICATE-----
|
2
|
-
MIIDLjCCAhYCCQDIZ/9hq/2pXjANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB
|
3
|
-
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
4
|
-
cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTYwNjA2MTk0MzI1WhcN
|
5
|
-
MTcwNjA2MTk0MzI1WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
|
6
|
-
ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwls
|
7
|
-
b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjFXrWqtRZ
|
8
|
-
EMOO/o6AxGbgDYMgg/7uxCFQJM5Z6C4II6D8V94FDyCd+J0LOK2hB+QUdFqpA1S9
|
9
|
-
6RW2MvIwdvRt03RJMgbfcUF0+w4ZItv2xrW9waCfCmLSRDZgcSATacEF6u9p2Vs+
|
10
|
-
o4J/cHacirSwjy4+m94CgkxtUFGtGcJaFqAZ6Cdj5WvQdJSiAI3x3gNC/UGA+5dL
|
11
|
-
sp8+vwWx+/TMc6nDBmoRW3GHeG/NApQSh01w3wDv0FmUaFQlA5WPya/Js+CyuYh1
|
12
|
-
miXbQJEjDnGGaJjnoyRAQpPrk72Jj+bnfOu9kxpzkuLJOsbaofRFkM+/Ar5U+bQz
|
13
|
-
uU0ErQ8Ih8MPAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBACL8HkKjW8kWLlW4TE5K
|
14
|
-
EcfsBad2Ah5ugTocJ/pLnr/YEo8uD91gECDtsuFTXul9n2M7U5jJmzbHZ63cjyC3
|
15
|
-
lb1BxJxUL7aIyaL61IMMcIJMWhC9VGnFUshMDNVBhuRkKs/QvaMD5KefKN1E9I2M
|
16
|
-
mZ72Yww0VihYwNOu3MTn8gUuy9eU6k/gTYPY7PJVh18QmR+Fs2MaaPp+bDwxiqML
|
17
|
-
0o2I6+0ZsqM3vFtcUjxjRASV5s+JkM34pTWFwUOl7TZv1YsxCKSz4f0BXDImZEvU
|
18
|
-
rwqFdlELp5WOG9LJsrszDRbf1wbFUsG1XXZpIBiWo3d6pOiIyRKrak1vKViNfYvI
|
19
|
-
W30=
|
20
|
-
-----END CERTIFICATE-----
|
data/example/keys/server.key
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
-----BEGIN RSA PRIVATE KEY-----
|
2
|
-
MIIEpAIBAAKCAQEAoxV61qrUWRDDjv6OgMRm4A2DIIP+7sQhUCTOWeguCCOg/Ffe
|
3
|
-
BQ8gnfidCzitoQfkFHRaqQNUvekVtjLyMHb0bdN0STIG33FBdPsOGSLb9sa1vcGg
|
4
|
-
nwpi0kQ2YHEgE2nBBervadlbPqOCf3B2nIq0sI8uPpveAoJMbVBRrRnCWhagGegn
|
5
|
-
Y+Vr0HSUogCN8d4DQv1BgPuXS7KfPr8Fsfv0zHOpwwZqEVtxh3hvzQKUEodNcN8A
|
6
|
-
79BZlGhUJQOVj8mvybPgsrmIdZol20CRIw5xhmiY56MkQEKT65O9iY/m53zrvZMa
|
7
|
-
c5LiyTrG2qH0RZDPvwK+VPm0M7lNBK0PCIfDDwIDAQABAoIBAQCZShphZsccRJ6c
|
8
|
-
bPdDX9iW5vyG9qsMgPwTGdWAOrXx3pN2PZ0pwjNVaRcsMgU6JHGlLEz/Kmtf6pQG
|
9
|
-
41I0bcuI48Yc+tHs+sadD1IMHHEHP3Yau8KfWyLSI129Pvf4Z2IQjuik5LJYaVbD
|
10
|
-
NNG4iMQYZS0Bmn6Oey0dXu62t0ywYa0qvbIDse/RmjTQSTipuvGg8/QEAeRGABv8
|
11
|
-
Nd4Esya0zuxk6hGaNp3hkjyRkeoC7RsBVJbFSnp6gSubPdXwrJyHfySKe9jvrDG3
|
12
|
-
Q/AzyHUh/6EODd5n66x0p6rq7oo9/PnLvZJY8jIGWG+aEp68RJyEgimrwll0rAWw
|
13
|
-
/buqijGRAoGBANimL8407fFirmct7BceavaeJfXPK5yWiOhVX0XlJ0phAFuaAxK3
|
14
|
-
5HVT7DD+KKV66g1jtS9FUVZGDiYFHlsdsYuHVYcRmr0h5rZr941obrDwNrM9Nf9C
|
15
|
-
0uehN5+n/FaeGoQLR3V4THoP3rlkYTlLpQnI5mKA19JukXnIiJM9ARUZAoGBAMC0
|
16
|
-
mcVsVuSKSFwURtQHHIufxL6SqC2kLTwIQ7exqejNYPCqCiif+ZWOmsTqbVGAGbMK
|
17
|
-
Ohak4oLwN5IGCl4jNQG+vWagREkx6OXSk5NYcfoNBrOm+0UoFRzoEA85s7Dy6PuD
|
18
|
-
tBucNZpt1sGauzkCSx7C8jj4ZlSwkv0XhBFfbTZnAoGBAK2wBjF+U6iq4YFM2rLq
|
19
|
-
KvzOa0Z3MdKXCOmiz//cKDTEMaI+heoyzZCWmIvqpzGLqirT3gUowH23Kk6m2eBY
|
20
|
-
nOdst0/S+Eha7nkfc9bFe8CUxHXMRAcCTs1ufYadCXtzw3RLCp4NtNpC8N+Wry9d
|
21
|
-
CtIeYz1jaCOHi0+kSoIobT65AoGAc6hxWkJp7ITqZQlucTdLdKmRheeztKEC3TMA
|
22
|
-
obGqDqWldww3SKarP431ahZhQjcmNYT/1DNmF7xhPe0OL+3llISMXJn4Ig4ogDdg
|
23
|
-
h2DgF3nV+eFQkfM6qLzHVrwFE0DXgI1NffzFV0hxSoW5tL+honbStkqv8EiCEBEb
|
24
|
-
HOovPCUCgYBpXuPARd2ycInAulVHijJmj2rmK7f41ZhVCWovYjcCWyeJyLIO7j+b
|
25
|
-
MBJZbmwpStJhEjW64nE2zZGWg2HCBbvZz5/SXIr3fp7qVXwpn1TvB/TJDf43t0oF
|
26
|
-
3caLgyQYoQCsVHKT3cU4s3wuog/DyHKh9FtRkcJrEy7h9Rrc+ModbA==
|
27
|
-
-----END RSA PRIVATE KEY-----
|
data/example/server.rb
DELETED
@@ -1,139 +0,0 @@
|
|
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
|
-
|
15
|
-
opts.on('-u', '--push', 'Push message') do |_v|
|
16
|
-
options[:push] = true
|
17
|
-
end
|
18
|
-
end.parse!
|
19
|
-
|
20
|
-
puts "Starting server on port #{options[:port]}"
|
21
|
-
server = TCPServer.new(options[:port])
|
22
|
-
|
23
|
-
if options[:secure]
|
24
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
25
|
-
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
|
26
|
-
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
|
27
|
-
|
28
|
-
ctx.ssl_version = :TLSv1_2
|
29
|
-
ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
30
|
-
ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
|
31
|
-
|
32
|
-
ctx.alpn_protocols = ['h2']
|
33
|
-
|
34
|
-
ctx.alpn_select_cb = lambda do |protocols|
|
35
|
-
raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?
|
36
|
-
DRAFT
|
37
|
-
end
|
38
|
-
|
39
|
-
ctx.ecdh_curves = 'P-256'
|
40
|
-
|
41
|
-
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
42
|
-
end
|
43
|
-
|
44
|
-
loop do
|
45
|
-
sock = server.accept
|
46
|
-
puts 'New TCP connection!'
|
47
|
-
|
48
|
-
conn = HTTP2::Server.new
|
49
|
-
conn.on(:frame) do |bytes|
|
50
|
-
# puts "Writing bytes: #{bytes.unpack("H*").first}"
|
51
|
-
sock.is_a?(TCPSocket) ? sock.sendmsg(bytes) : sock.write(bytes)
|
52
|
-
end
|
53
|
-
conn.on(:frame_sent) do |frame|
|
54
|
-
puts "Sent frame: #{frame.inspect}"
|
55
|
-
end
|
56
|
-
conn.on(:frame_received) do |frame|
|
57
|
-
puts "Received frame: #{frame.inspect}"
|
58
|
-
end
|
59
|
-
|
60
|
-
conn.on(:stream) do |stream|
|
61
|
-
log = Logger.new(stream.id)
|
62
|
-
req, buffer = {}, ''
|
63
|
-
|
64
|
-
stream.on(:active) { log.info 'client opened new stream' }
|
65
|
-
stream.on(:close) { log.info 'stream closed' }
|
66
|
-
|
67
|
-
stream.on(:headers) do |h|
|
68
|
-
req = Hash[*h.flatten]
|
69
|
-
log.info "request headers: #{h}"
|
70
|
-
end
|
71
|
-
|
72
|
-
stream.on(:data) do |d|
|
73
|
-
log.info "payload chunk: <<#{d}>>"
|
74
|
-
buffer << d
|
75
|
-
end
|
76
|
-
|
77
|
-
stream.on(:half_close) do
|
78
|
-
log.info 'client closed its end of the stream'
|
79
|
-
|
80
|
-
response = nil
|
81
|
-
if req[':method'] == 'POST'
|
82
|
-
log.info "Received POST request, payload: #{buffer}"
|
83
|
-
response = "Hello HTTP 2.0! POST payload: #{buffer}"
|
84
|
-
else
|
85
|
-
log.info 'Received GET request'
|
86
|
-
response = 'Hello HTTP 2.0! GET request'
|
87
|
-
end
|
88
|
-
|
89
|
-
stream.headers({
|
90
|
-
':status' => '200',
|
91
|
-
'content-length' => response.bytesize.to_s,
|
92
|
-
'content-type' => 'text/plain',
|
93
|
-
}, end_stream: false)
|
94
|
-
|
95
|
-
if options[:push]
|
96
|
-
push_streams = []
|
97
|
-
|
98
|
-
# send 10 promises
|
99
|
-
10.times do |i|
|
100
|
-
puts 'sending push'
|
101
|
-
|
102
|
-
head = { ':method' => 'GET',
|
103
|
-
':authority' => 'localhost',
|
104
|
-
':scheme' => 'https',
|
105
|
-
':path' => "/other_resource/#{i}" }
|
106
|
-
|
107
|
-
stream.promise(head) do |push|
|
108
|
-
push.headers(':status' => '200', 'content-type' => 'text/plain', 'content-length' => '11')
|
109
|
-
push_streams << push
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# split response into multiple DATA frames
|
115
|
-
stream.data(response.slice!(0, 5), end_stream: false)
|
116
|
-
stream.data(response)
|
117
|
-
|
118
|
-
if options[:push]
|
119
|
-
push_streams.each_with_index do |push, i|
|
120
|
-
sleep 1
|
121
|
-
push.data("push_data #{i}")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
|
128
|
-
data = sock.readpartial(1024)
|
129
|
-
# puts "Received bytes: #{data.unpack("H*").first}"
|
130
|
-
|
131
|
-
begin
|
132
|
-
conn << data
|
133
|
-
rescue StandardError => e
|
134
|
-
puts "#{e.class} exception: #{e.message} - closing socket."
|
135
|
-
e.backtrace.each { |l| puts "\t" + l }
|
136
|
-
sock.close
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
data/example/upgrade_client.rb
DELETED
@@ -1,153 +0,0 @@
|
|
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(headers)
|
77
|
-
@headers.merge!(headers)
|
78
|
-
puts "received headers: #{headers}"
|
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
|
-
rescue StandardError => e
|
148
|
-
puts "#{e.class} exception: #{e.message} - closing socket."
|
149
|
-
e.backtrace.each { |l| puts "\t" + l }
|
150
|
-
conn.close
|
151
|
-
sock.close
|
152
|
-
end
|
153
|
-
end
|