http-2 0.11.0 → 1.0.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.
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