http-2 0.11.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -9
  3. data/lib/http/2/base64.rb +45 -0
  4. data/lib/http/2/client.rb +19 -6
  5. data/lib/http/2/connection.rb +235 -163
  6. data/lib/http/2/emitter.rb +7 -5
  7. data/lib/http/2/error.rb +24 -1
  8. data/lib/http/2/extensions.rb +53 -0
  9. data/lib/http/2/flow_buffer.rb +91 -33
  10. data/lib/http/2/framer.rb +184 -157
  11. data/lib/http/2/header/compressor.rb +157 -0
  12. data/lib/http/2/header/decompressor.rb +144 -0
  13. data/lib/http/2/header/encoding_context.rb +337 -0
  14. data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
  15. data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
  16. data/lib/http/2/header.rb +35 -0
  17. data/lib/http/2/server.rb +47 -20
  18. data/lib/http/2/stream.rb +130 -61
  19. data/lib/http/2/version.rb +3 -1
  20. data/lib/http/2.rb +14 -13
  21. data/sig/client.rbs +9 -0
  22. data/sig/connection.rbs +93 -0
  23. data/sig/emitter.rbs +13 -0
  24. data/sig/error.rbs +35 -0
  25. data/sig/extensions.rbs +5 -0
  26. data/sig/flow_buffer.rbs +21 -0
  27. data/sig/frame_buffer.rbs +13 -0
  28. data/sig/framer.rbs +54 -0
  29. data/sig/header/compressor.rbs +27 -0
  30. data/sig/header/decompressor.rbs +22 -0
  31. data/sig/header/encoding_context.rbs +34 -0
  32. data/sig/header/huffman.rbs +9 -0
  33. data/sig/header.rbs +27 -0
  34. data/sig/next.rbs +101 -0
  35. data/sig/server.rbs +12 -0
  36. data/sig/stream.rbs +91 -0
  37. metadata +38 -79
  38. data/.autotest +0 -20
  39. data/.coveralls.yml +0 -1
  40. data/.gitignore +0 -20
  41. data/.gitmodules +0 -3
  42. data/.rspec +0 -5
  43. data/.rubocop.yml +0 -93
  44. data/.rubocop_todo.yml +0 -131
  45. data/.travis.yml +0 -17
  46. data/Gemfile +0 -16
  47. data/Guardfile +0 -18
  48. data/Guardfile.h2spec +0 -12
  49. data/LICENSE +0 -21
  50. data/Rakefile +0 -49
  51. data/example/Gemfile +0 -3
  52. data/example/README.md +0 -44
  53. data/example/client.rb +0 -122
  54. data/example/helper.rb +0 -19
  55. data/example/keys/server.crt +0 -20
  56. data/example/keys/server.key +0 -27
  57. data/example/server.rb +0 -139
  58. data/example/upgrade_client.rb +0 -153
  59. data/example/upgrade_server.rb +0 -203
  60. data/http-2.gemspec +0 -22
  61. data/lib/http/2/buffer.rb +0 -76
  62. data/lib/http/2/compressor.rb +0 -572
  63. data/lib/tasks/generate_huffman_table.rb +0 -166
  64. data/spec/buffer_spec.rb +0 -28
  65. data/spec/client_spec.rb +0 -188
  66. data/spec/compressor_spec.rb +0 -666
  67. data/spec/connection_spec.rb +0 -681
  68. data/spec/emitter_spec.rb +0 -54
  69. data/spec/framer_spec.rb +0 -487
  70. data/spec/h2spec/h2spec.darwin +0 -0
  71. data/spec/h2spec/output/non_secure.txt +0 -317
  72. data/spec/helper.rb +0 -147
  73. data/spec/hpack_test_spec.rb +0 -84
  74. data/spec/huffman_spec.rb +0 -68
  75. data/spec/server_spec.rb +0 -52
  76. data/spec/stream_spec.rb +0 -878
  77. data/spec/support/deep_dup.rb +0 -55
  78. data/spec/support/duplicable.rb +0 -98
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
@@ -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-----
@@ -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
@@ -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
@@ -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