http-2 0.6.1 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.autotest +2 -1
- data/Gemfile +8 -1
- data/README.md +27 -21
- data/example/README.md +53 -0
- data/example/client.rb +88 -30
- data/example/helper.rb +5 -0
- data/example/keys/mycert.pem +24 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +47 -8
- data/http-2.gemspec +0 -2
- data/lib/http/2.rb +2 -0
- data/lib/http/2/buffer.rb +21 -4
- data/lib/http/2/client.rb +50 -0
- data/lib/http/2/compressor.rb +197 -181
- data/lib/http/2/connection.rb +57 -83
- data/lib/http/2/emitter.rb +2 -2
- data/lib/http/2/error.rb +5 -0
- data/lib/http/2/framer.rb +32 -31
- data/lib/http/2/server.rb +55 -0
- data/lib/http/2/stream.rb +1 -1
- data/lib/http/2/version.rb +1 -1
- data/spec/buffer_spec.rb +23 -0
- data/spec/client_spec.rb +93 -0
- data/spec/compressor_spec.rb +89 -80
- data/spec/connection_spec.rb +24 -75
- data/spec/emitter_spec.rb +8 -0
- data/spec/framer_spec.rb +36 -40
- data/spec/helper.rb +6 -2
- data/spec/server_spec.rb +50 -0
- data/spec/stream_spec.rb +23 -30
- metadata +13 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: debc6f11031e879547dd45605849500e45fd1142
|
4
|
+
data.tar.gz: 61e60a0b2b831553a9ecca42b069c032afe12e30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7c36dec0419a45e75f8664e6ac4ede157c49057d51bd7f25f795139313048978e4c053e49d90f0140731ec36b2a7df68fd075c50bb1f9cf98169b3192571ddd
|
7
|
+
data.tar.gz: 9ab31eb78ec61a37139efeba7185a87cd742588f30a456b35bbeb8935e50487cd948be09e9fed68ba1510ef91db88916815cc30ab41941634b567be59b56099c
|
data/.autotest
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,31 +1,41 @@
|
|
1
1
|
# HTTP-2
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/http-2.png)](http://rubygems.org/gems/http-2)
|
4
|
+
[![Build Status](https://travis-ci.org/igrigorik/http-2.png?branch=master)](https://travis-ci.org/igrigorik/http-2)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/igrigorik/http-2/badge.png)](https://coveralls.io/r/igrigorik/http-2)
|
6
|
+
|
3
7
|
Pure ruby, framework and transport agnostic implementation of [HTTP 2.0 protocol](http://tools.ietf.org/html/draft-ietf-httpbis-http2) with support for:
|
4
8
|
|
5
9
|
* [Binary framing](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_binary_framing_layer) parsing and encoding
|
6
10
|
* [Stream multiplexing](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_STREAMS_MESSAGES_FRAMES) and [prioritization](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PRIORITIZATION)
|
7
11
|
* Connection and stream [flow control](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_flow_control)
|
8
12
|
* [Header compression](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_HEADER_COMPRESSION) and [server push](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PUSH)
|
9
|
-
* Connection and stream management
|
13
|
+
* Connection and stream management
|
14
|
+
* And more... see [API docs](http://www.rubydoc.info/github/igrigorik/http-2/frames)
|
10
15
|
|
11
16
|
Current implementation (see [HPBN chapter for HTTP 2.0 overview](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html)), is based on:
|
12
17
|
|
13
18
|
* [draft-ietf-httpbis-http2-06](http://tools.ietf.org/html/draft-ietf-httpbis-http2-06)
|
14
|
-
* [draft-ietf-httpbis-header-compression-
|
19
|
+
* [draft-ietf-httpbis-header-compression-03](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03)
|
15
20
|
|
16
21
|
_Note: the underlying specifications are still evolving, expect APIs to change and evolve also..._
|
17
22
|
|
18
23
|
|
19
24
|
## Getting started
|
20
25
|
|
26
|
+
```bash
|
27
|
+
$> gem install http-2
|
28
|
+
```
|
29
|
+
|
21
30
|
This implementation makes no assumptions as how the data is delivered: it could be a regular Ruby TCP socket, your custom eventloop, or whatever other transport you wish to use - e.g. ZeroMQ, [avian carriers](http://www.ietf.org/rfc/rfc1149.txt), etc.
|
22
31
|
|
23
32
|
Your code is responsible for feeding data into the parser, which performs all of the necessary HTTP 2.0 decoding, state management and the rest, and vice versa, the parser will emit bytes (encoded HTTP 2.0 frames) that you can then route to the destination. Roughly, this works as follows:
|
24
33
|
|
25
34
|
```ruby
|
35
|
+
require 'http/2'
|
26
36
|
socket = YourTransport.new
|
27
37
|
|
28
|
-
conn = HTTP2::
|
38
|
+
conn = HTTP2::Client.new
|
29
39
|
conn.on(:frame) {|bytes| socket << bytes }
|
30
40
|
|
31
41
|
while bytes = socket.read
|
@@ -38,11 +48,11 @@ Checkout provided [client](https://github.com/igrigorik/http-2/blob/master/examp
|
|
38
48
|
|
39
49
|
### Connection lifecycle management
|
40
50
|
|
41
|
-
|
51
|
+
Depending on the role of the endpoint you must initialize either a [Client](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Client) or a [Server](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Server) object. Doing so picks the appropriate header compression / decompression algorithms and stream management logic. From there, you can subscribe to connection level events, or invoke appropriate APIs to allocate new streams and manage the lifecycle. For example:
|
42
52
|
|
43
53
|
```ruby
|
44
54
|
# - Server ---------------
|
45
|
-
server = HTTP2::
|
55
|
+
server = HTTP2::Server.new
|
46
56
|
|
47
57
|
server.on(:stream) { |stream| ... } # process inbound stream
|
48
58
|
server.on(:frame) { |bytes| ... } # encoded HTTP 2.0 frames
|
@@ -51,7 +61,7 @@ server.ping { ... } # run liveness check, process pong response
|
|
51
61
|
server.goaway # send goaway frame to the client
|
52
62
|
|
53
63
|
# - Client ---------------
|
54
|
-
client = HTTP2::
|
64
|
+
client = HTTP2::Client.new
|
55
65
|
client.on(:promise) { |stream| ... } # process push promise
|
56
66
|
|
57
67
|
stream = client.new_stream # allocate new stream
|
@@ -113,7 +123,7 @@ The good news is, all of the stream management, and state transitions, and error
|
|
113
123
|
For sake of example, let's take a look at a simple server implementation:
|
114
124
|
|
115
125
|
```ruby
|
116
|
-
conn = HTTP2::
|
126
|
+
conn = HTTP2::Server.new
|
117
127
|
|
118
128
|
# emits new streams opened by the client
|
119
129
|
conn.on(:stream) do |stream|
|
@@ -141,7 +151,7 @@ conn.on(:stream) do |stream|
|
|
141
151
|
end
|
142
152
|
```
|
143
153
|
|
144
|
-
Events emitted by the
|
154
|
+
Events emitted by the [Stream object](http://www.rubydoc.info/github/igrigorik/http-2/HTTP2/Stream):
|
145
155
|
|
146
156
|
<table>
|
147
157
|
<tr>
|
@@ -180,13 +190,13 @@ Events emitted by the stream object:
|
|
180
190
|
Each HTTP 2.0 [stream has a priority value](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PRIORITIZATION) that can be sent when the new stream is initialized, and optionally reprioritized later:
|
181
191
|
|
182
192
|
```ruby
|
183
|
-
client = HTTP2::
|
193
|
+
client = HTTP2::Client.new
|
184
194
|
|
185
195
|
default_priority_stream = client.new_stream
|
186
|
-
custom_priority_stream = client.new_stream(
|
196
|
+
custom_priority_stream = client.new_stream(priority: 42)
|
187
197
|
|
188
198
|
# sometime later: change priority value
|
189
|
-
custom_priority_stream.
|
199
|
+
custom_priority_stream.reprioritize(32000) # emits PRIORITY frame
|
190
200
|
```
|
191
201
|
|
192
202
|
On the opposite side, the server can optimize its stream processing order or resource allocation by accessing the stream priority value (`stream.priority`).
|
@@ -213,10 +223,8 @@ stream.window_update(2048) # increment stream window by 2048 bytes
|
|
213
223
|
Alternatively, flow control can be disabled by emitting an appropriate settings frame on the connection:
|
214
224
|
|
215
225
|
```ruby
|
216
|
-
|
217
|
-
|
218
|
-
settings_flow_control_options: 1 # disable flow control
|
219
|
-
})
|
226
|
+
# limit number of concurrent streams to 100 and disable flow control
|
227
|
+
conn.settings(streams: 100, window: Float::INFINITY)
|
220
228
|
```
|
221
229
|
|
222
230
|
### Server push
|
@@ -224,7 +232,7 @@ conn.settings({
|
|
224
232
|
An HTTP 2.0 server can [send multiple replies](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#HTTP2_PUSH) to a single client request. To do so, first it emits a "push promise" frame which contains the headers of the promised resource, followed by the response to the original request, as well as promised resource payloads (which may be interleaved). A simple example is in order:
|
225
233
|
|
226
234
|
```ruby
|
227
|
-
conn = HTTP2::
|
235
|
+
conn = HTTP2::Server.new
|
228
236
|
|
229
237
|
conn.on(:stream) do |stream|
|
230
238
|
stream.on(:headers) { |head| ... }
|
@@ -258,10 +266,10 @@ conn.on(:stream) do |stream|
|
|
258
266
|
end
|
259
267
|
```
|
260
268
|
|
261
|
-
When a new push promise stream is sent by the server, the client is
|
269
|
+
When a new push promise stream is sent by the server, the client is notified via the `:promise` event:
|
262
270
|
|
263
271
|
```ruby
|
264
|
-
conn = HTTP2::
|
272
|
+
conn = HTTP2::Client.new
|
265
273
|
conn.on(:promise) do |push|
|
266
274
|
# process push stream
|
267
275
|
end
|
@@ -270,9 +278,7 @@ end
|
|
270
278
|
The client can cancel any given push stream (via `.close`), or disable server push entirely by sending the appropriate settings frame (note that below setting only impacts server > client direction):
|
271
279
|
|
272
280
|
```ruby
|
273
|
-
client.settings(
|
274
|
-
settings_max_concurrent_streams: 0 # set number of server initiated streams to 0 (aka, disable push)
|
275
|
-
})
|
281
|
+
client.settings(streams: 0) # setting max limit to 0 disables server push
|
276
282
|
```
|
277
283
|
|
278
284
|
### License
|
data/example/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
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
|
+
# TLS + NPN negotiation
|
12
|
+
$> ruby server.rb --secure
|
13
|
+
$> ruby client.rb https://localhost:8080/ # GET
|
14
|
+
$> ...
|
15
|
+
```
|
16
|
+
|
17
|
+
### [nghttp2](https://github.com/tatsuhiro-t/nghttp2) (HTTP/2.0 C Library)
|
18
|
+
|
19
|
+
Public test server: http://106.186.112.116 (Upgrade + Direct)
|
20
|
+
|
21
|
+
```bash
|
22
|
+
# Direct request (http-2 > nghttp2)
|
23
|
+
$> ruby client.rb http://106.186.112.116/
|
24
|
+
|
25
|
+
# TLS + NPN request (http-2 > nghttp2)
|
26
|
+
$> ruby client.rb https://106.186.112.116/
|
27
|
+
|
28
|
+
# Direct request (nghttp2 > http-2)
|
29
|
+
$> ruby server.rb
|
30
|
+
$> nghttp -vnu http://localhost:8080 # Direct request to Ruby server
|
31
|
+
```
|
32
|
+
|
33
|
+
### [node-http2](https://github.com/molnarg/node-http2) (node.js client/server)
|
34
|
+
|
35
|
+
```bash
|
36
|
+
# NPN + GET request (http-2 > node-http2)
|
37
|
+
$> ruby client.rb https://gabor.molnar.es:8080/
|
38
|
+
|
39
|
+
# NPN + GET request with server push (http-2 > node-http2)
|
40
|
+
$> ruby client.rb https://gabor.molnar.es:8080/test/push.html
|
41
|
+
|
42
|
+
# NPN + POST request (http-2 > node-http2)
|
43
|
+
$> ruby client.rb https://gabor.molnar.es:8080/post -d'some data'
|
44
|
+
```
|
45
|
+
|
46
|
+
### Twitter (Java server)
|
47
|
+
|
48
|
+
```bash
|
49
|
+
# NPN + GET request (http-2 > twitter)
|
50
|
+
$> ruby client.rb https://twitter.com/
|
51
|
+
```
|
52
|
+
|
53
|
+
For a complete list of current implementations, see [http2 wiki](https://github.com/http2/http2-spec/wiki/Implementations).
|
data/example/client.rb
CHANGED
@@ -1,46 +1,104 @@
|
|
1
1
|
require_relative 'helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
9
|
end
|
10
|
+
end.parse!
|
10
11
|
|
11
|
-
stream = conn.new_stream
|
12
|
-
log = Logger.new(stream.id)
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
13
|
+
uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
|
14
|
+
tcp = TCPSocket.new(uri.host, uri.port)
|
15
|
+
sock = nil
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
if uri.scheme == 'https'
|
18
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
19
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
20
|
+
|
21
|
+
ctx.npn_protocols = [DRAFT]
|
22
|
+
ctx.npn_select_cb = lambda do |protocols|
|
23
|
+
puts "NPN protocols supported by server: #{protocols}"
|
24
|
+
DRAFT if protocols.include? DRAFT
|
25
|
+
end
|
26
|
+
|
27
|
+
sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
28
|
+
sock.sync_close = true
|
29
|
+
sock.hostname = uri.hostname
|
30
|
+
sock.connect
|
31
|
+
|
32
|
+
if sock.npn_protocol != DRAFT
|
33
|
+
puts "Failed to negotiate #{DRAFT} via NPN"
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
else
|
37
|
+
sock = tcp
|
38
|
+
end
|
39
|
+
|
40
|
+
conn = HTTP2::Client.new
|
41
|
+
conn.on(:frame) do |bytes|
|
42
|
+
puts "Sending bytes: #{bytes.inspect}"
|
43
|
+
sock.print bytes
|
44
|
+
sock.flush
|
45
|
+
end
|
46
|
+
|
47
|
+
stream = conn.new_stream
|
48
|
+
log = Logger.new(stream.id)
|
22
49
|
|
23
|
-
|
24
|
-
|
50
|
+
conn.on(:promise) do |promise|
|
51
|
+
promise.on(:headers) do |h|
|
52
|
+
log.info "promise headers: #{h}"
|
25
53
|
end
|
26
54
|
|
27
|
-
|
28
|
-
log.info "
|
55
|
+
promise.on(:data) do |d|
|
56
|
+
log.info "promise data chunk: <<#{d.size}>>"
|
29
57
|
end
|
58
|
+
end
|
30
59
|
|
31
|
-
|
32
|
-
stream
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
60
|
+
stream.on(:close) do
|
61
|
+
log.info "stream closed"
|
62
|
+
sock.close
|
63
|
+
end
|
64
|
+
|
65
|
+
stream.on(:half_close) do
|
66
|
+
log.info "closing client-end of the stream"
|
67
|
+
end
|
68
|
+
|
69
|
+
stream.on(:headers) do |h|
|
70
|
+
log.info "response headers: #{h}"
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
stream.on(:data) do |d|
|
75
|
+
log.info "response data chunk: <<#{d}>>"
|
76
|
+
end
|
77
|
+
|
78
|
+
head = {
|
79
|
+
":scheme" => uri.scheme,
|
80
|
+
":method" => (options[:payload].nil? ? "get" : "post"),
|
81
|
+
":host" => [uri.host, uri.port].join(':'),
|
82
|
+
":path" => uri.path,
|
83
|
+
"accept" => "*/*"
|
84
|
+
}
|
85
|
+
|
86
|
+
puts "Sending HTTP 2.0 request"
|
87
|
+
if head[":method"] == "get"
|
88
|
+
stream.headers(head, end_stream: true)
|
89
|
+
else
|
90
|
+
stream.headers(head, end_stream: false)
|
91
|
+
stream.data(options[:payload])
|
92
|
+
end
|
38
93
|
|
39
|
-
|
94
|
+
while !sock.closed? && !sock.eof?
|
95
|
+
data = sock.read_nonblock(1024)
|
96
|
+
# puts "Received bytes: #{data.inspect}"
|
40
97
|
|
41
|
-
|
42
|
-
data = sock.readpartial(1024)
|
43
|
-
puts "Received bytes: #{data.inspect}"
|
98
|
+
begin
|
44
99
|
conn << data
|
100
|
+
rescue Exception => e
|
101
|
+
puts "Exception: #{e}, #{e.message} - closing socket."
|
102
|
+
sock.close
|
45
103
|
end
|
46
104
|
end
|
data/example/helper.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIID9DCCAtygAwIBAgIJAKIwkCCNJr2mMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV
|
3
|
+
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
4
|
+
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMjA2MjQxODE4
|
5
|
+
MTZaFw0xMzA2MjQxODE4MTZaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l
|
6
|
+
LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
|
7
|
+
BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOTQ
|
8
|
+
eHgUpZnggH2RwBeghuf86/7wNeO4DIJacLrmCynA871ioOvxzPx5jPTV6dkkitMT
|
9
|
+
lNroneNa/BbRHHOVUvCtuJyKXEiFyWs/jCYAZ335uMYtDK2FAHRvNgXMM1YZTpbM
|
10
|
+
CSIteVsWIAuNFAWwQS8Bd9oACUS1P69eNbA1BBOqnaYoZT1D+JD4bp9kpBYqXJtf
|
11
|
+
wOgHGzPOVatqzJDgOqSwYVz1ZETVopv+TMBght938iVoow2cHnJ2Zj/n/jBkG8E9
|
12
|
+
FNlmc67WqQkJsS16E9sEntJOSNldBRsjMFux4E+g7wzyn9tFJ0nYJ/rklWa24BN7
|
13
|
+
Vg9diYljQNLLSMv8/tcCAwEAAaOBvjCBuzAdBgNVHQ4EFgQUqBtvGKKXRaXzFV16
|
14
|
+
W1uMzroa8SkwgYsGA1UdIwSBgzCBgIAUqBtvGKKXRaXzFV16W1uMzroa8SmhXaRb
|
15
|
+
MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ
|
16
|
+
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAKIw
|
17
|
+
kCCNJr2mMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBALji7DAGBRU9
|
18
|
+
x6/zw7EcEDlutGv6qp1VasJzSexVCZAr5c7vSDbdM+wlAxvwBguoFNLvrIvBxUMN
|
19
|
+
pf/lvig2Q++lGTiqu5VodnmJXnqze7PO2Rdq9Yqgkva7qVFuGrYwGTXfiuz5LqGV
|
20
|
+
BWz/i1i8EwJmO+p0yLk+r1hjBaeAqfbZtt1ACeZgu/8zaI9ypb2vpwOyW5TUJ8hk
|
21
|
+
S+ZAX1Pd5OMqerD6DTfD/McHkWnC3ilcW4ZQIIJABKGxPc7k9Gyod9PZIPJS/ER7
|
22
|
+
7koJ84u8esvnaP3DGHIuWngMpQcgMzJbD5bNX+C0DoaxkWre8u7afCW68a58RP19
|
23
|
+
LTlr/GlI51c=
|
24
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEA5NB4eBSlmeCAfZHAF6CG5/zr/vA147gMglpwuuYLKcDzvWKg
|
3
|
+
6/HM/HmM9NXp2SSK0xOU2uid41r8FtEcc5VS8K24nIpcSIXJaz+MJgBnffm4xi0M
|
4
|
+
rYUAdG82BcwzVhlOlswJIi15WxYgC40UBbBBLwF32gAJRLU/r141sDUEE6qdpihl
|
5
|
+
PUP4kPhun2SkFipcm1/A6AcbM85Vq2rMkOA6pLBhXPVkRNWim/5MwGCG33fyJWij
|
6
|
+
DZwecnZmP+f+MGQbwT0U2WZzrtapCQmxLXoT2wSe0k5I2V0FGyMwW7HgT6DvDPKf
|
7
|
+
20UnSdgn+uSVZrbgE3tWD12JiWNA0stIy/z+1wIDAQABAoIBABSU5/EtMkQoHIav
|
8
|
+
AI9fgiMF7hhtdPt5x65GAlPdc22bDJGheIYgpuai7Fntj+5XSiF4ZnBWcjVMLtbC
|
9
|
+
koOXD/HUPoHeNDTVy+tYuPuGF8kOGF/DF5vYFdVjV4Gn/4okFpyb18p6OqtFzzYa
|
10
|
+
x41HcGWRBT3XuP20K/lTSRMDgc1e5N9bmoOONJqT+K1ak0Dv2Ifl5RTBWbmYymgR
|
11
|
+
30cgDkiazCNjhRCCCBX8cctB4ULiOvqdksn5Ln05jvezd1Wog/ryr4SI+HMmAroZ
|
12
|
+
dB+F8kODO792/ahR8DusQUVpwVbHqAXOVHmtplMZ4DlIgw+qY5pRawkvdetq/Ghy
|
13
|
+
fHAInsECgYEA/U4uNsghZJiWnpZ999esCbHwM6orgX561Nx/WsmKGv0pjslZNDrx
|
14
|
+
5Awd0EQo/NW59CUW5RcHnK+ayAsQ1xgN4HB97xYAj5v0xEzhQw8fg39fc/4gja+j
|
15
|
+
F89wK3nfX1F4PpisN6eir+z2UcUJLT4rIsFa57R7TbLV6eV/nhxoauECgYEA5z+Y
|
16
|
+
ITSdgwsS8FR+8ZL/UvdmkDW34gQUZ3QEAkJZfkCaHXq90tgIHDBm8dZBKEuDHJSN
|
17
|
+
yJQ4VeHzBD9q7pdz/qDXAw5Jqgz01ToWWuGvCbcqvH6u1UrVvUhgr0JbyUZgrL4r
|
18
|
+
/c5buWHS+IKwtBqWG1oo4kclfz3TIjtLXiVDmLcCgYB+/K2wav5KpzCDSqDWGjo2
|
19
|
+
Fg18aSgsYBMGGZCDHBxvUVF/MrPUumQ/1k8v9KuzrRXvLpTevn/jbimjdeC4ZGe4
|
20
|
+
h8yqipY3aJD5xCz96Fv9GWLqDJGXVmDl8+mg8hUofPhSMUnNEO4/UgVeku/5zXvk
|
21
|
+
jZicJl/WYPxaqOIkistSIQKBgQCoLuhFvi6QkA1GHS32JCLuBGDjoS4Lg0wTsZz4
|
22
|
+
x6iu2e08Y3iLT/MWDV3RpTHeTI0ezCwSJTqTu7Ey9ayfuibymafG4S1SL/og2g5I
|
23
|
+
KrtTJZQ/YyNknPi2oV0wGeMHj9ffyq/T97FeMndtph893dguLHRvna73y88ypk06
|
24
|
+
O3/eIQKBgH10X3RoSDr04vVLFPjJ4beRj4SQA7BVUCio5MSrqkh2aTW0nFv+DiVm
|
25
|
+
M5nqfiH5QjO1NkKXrqUYAFVdAxcLRPXSAMHK0G61HyWDKyUEwAqWQ4sZGgFrzFUb
|
26
|
+
tihmVFKrXyzeMLumwn6TNCGVPYGAWw4FgHq59Hr+bAMp2g80Nmyj
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/example/server.rb
CHANGED
@@ -1,10 +1,35 @@
|
|
1
1
|
require_relative 'helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
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
|
5
30
|
puts "New TCP connection!"
|
6
31
|
|
7
|
-
conn = HTTP2::
|
32
|
+
conn = HTTP2::Server.new
|
8
33
|
conn.on(:frame) do |bytes|
|
9
34
|
puts "Writing bytes: #{bytes.inspect}"
|
10
35
|
sock.write bytes
|
@@ -12,12 +37,13 @@ Socket.tcp_server_loop(8080) do |sock|
|
|
12
37
|
|
13
38
|
conn.on(:stream) do |stream|
|
14
39
|
log = Logger.new(stream.id)
|
15
|
-
buffer =
|
40
|
+
req, buffer = {}, ''
|
16
41
|
|
17
42
|
stream.on(:active) { log.info "cliend opened new stream" }
|
18
43
|
stream.on(:close) { log.info "stream closed" }
|
19
44
|
|
20
45
|
stream.on(:headers) do |h|
|
46
|
+
req = h
|
21
47
|
log.info "request headers: #{h}"
|
22
48
|
end
|
23
49
|
|
@@ -27,10 +53,17 @@ Socket.tcp_server_loop(8080) do |sock|
|
|
27
53
|
end
|
28
54
|
|
29
55
|
stream.on(:half_close) do
|
30
|
-
log.info "client closed its end of the stream
|
31
|
-
|
56
|
+
log.info "client closed its end of the stream"
|
57
|
+
|
58
|
+
response = nil
|
59
|
+
if req[":method"] == "post"
|
60
|
+
log.info "Received POST request, payload: #{buffer}"
|
61
|
+
response = "Hello HTTP 2.0! POST payload: #{buffer}"
|
62
|
+
else
|
63
|
+
log.info "Received GET request"
|
64
|
+
response = "Hello HTTP 2.0! GET request"
|
65
|
+
end
|
32
66
|
|
33
|
-
response = "Hello HTTP 2.0! echo: #{buffer}"
|
34
67
|
stream.headers({
|
35
68
|
":status" => "200",
|
36
69
|
"content-length" => response.bytesize.to_s,
|
@@ -45,6 +78,12 @@ Socket.tcp_server_loop(8080) do |sock|
|
|
45
78
|
|
46
79
|
while !sock.closed? && !sock.eof?
|
47
80
|
data = sock.readpartial(1024)
|
48
|
-
|
81
|
+
|
82
|
+
begin
|
83
|
+
conn << data
|
84
|
+
rescue Exception => e
|
85
|
+
puts "Exception: #{e}, #{e.message} - closing socket."
|
86
|
+
sock.close
|
87
|
+
end
|
49
88
|
end
|
50
89
|
end
|