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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb3959b06060d626b9065b6b267fc1e4f3906742
4
- data.tar.gz: c7da3c39b1829b0458d6d550df7db911bcd69d29
3
+ metadata.gz: debc6f11031e879547dd45605849500e45fd1142
4
+ data.tar.gz: 61e60a0b2b831553a9ecca42b069c032afe12e30
5
5
  SHA512:
6
- metadata.gz: 293393327feb7f5aafd7017fb63ddb1a332957c8c5221c26050cb5045edf7067b8c2094ee8587227262c3be427838bab46ebd16c33ae8c0e05f8e08482d222ee
7
- data.tar.gz: d619c4939dfe5daa0d861d190d9f83368cfd352e46e22b897eaca17318e12550612b855a0a1bf7f860d5443776d3ca9d1fe5e873bdde452c6e2b29c21b568292
6
+ metadata.gz: b7c36dec0419a45e75f8664e6ac4ede157c49057d51bd7f25f795139313048978e4c053e49d90f0140731ec36b2a7df68fd075c50bb1f9cf98169b3192571ddd
7
+ data.tar.gz: 9ab31eb78ec61a37139efeba7185a87cd742588f30a456b35bbeb8935e50487cd948be09e9fed68ba1510ef91db88916815cc30ab41941634b567be59b56099c
data/.autotest CHANGED
@@ -1,8 +1,9 @@
1
1
  require 'autotest/growl'
2
2
 
3
3
  Autotest.add_hook(:initialize) {|at|
4
- at.add_exception %r{^\.git}
4
+ at.add_exception %r{^\.git|\.yardoc}
5
5
  at.add_exception %r{^./tmp}
6
+ at.add_exception %r{coverage}
6
7
 
7
8
  at.clear_mappings
8
9
 
data/Gemfile CHANGED
@@ -1,4 +1,11 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in http2-parser.gemspec
3
+ gem 'rake'
4
+ gem 'yard'
5
+
6
+ group :test do
7
+ gem 'coveralls', :require => false
8
+ gem 'rspec'
9
+ end
10
+
4
11
  gemspec
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, and other HTTP 2.0 goodies...
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-01](http://tools.ietf.org/html/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::Connection.new(:client)
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
- When the connection object is instantiated you must specify its role (`:client` or `:server`) to initialize 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:
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::Connection.new(:server)
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::Connection.new(:client)
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::Connection.new(:server)
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 stream object:
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::Connection.new(:client)
193
+ client = HTTP2::Client.new
184
194
 
185
195
  default_priority_stream = client.new_stream
186
- custom_priority_stream = client.new_stream(42) # priority: 42
196
+ custom_priority_stream = client.new_stream(priority: 42)
187
197
 
188
198
  # sometime later: change priority value
189
- custom_priority_stream.priority = 32000 # emits PRIORITY frame
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
- conn.settings({
217
- settings_max_concurrent_streams: 100, # limit number of concurrent streams
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::Connection.new(:server)
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 notifed via the `:promise` event:
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::Connection.new(:client)
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
@@ -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).
@@ -1,46 +1,104 @@
1
1
  require_relative 'helper'
2
2
 
3
- Addrinfo.tcp("localhost", 8080).connect do |sock|
4
- conn = HTTP2::Connection.new(:client)
5
- conn.on(:frame) do |bytes|
6
- puts "Sending bytes: #{bytes.inspect}"
7
- sock.print bytes
8
- sock.flush
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
- stream.on(:close) do
15
- log.info "stream closed"
16
- sock.close
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
- stream.on(:half_close) do
20
- log.info "closing client-end of the stream"
21
- end
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
- stream.on(:headers) do |h|
24
- log.info "response headers: #{h}"
50
+ conn.on(:promise) do |promise|
51
+ promise.on(:headers) do |h|
52
+ log.info "promise headers: #{h}"
25
53
  end
26
54
 
27
- stream.on(:data) do |d|
28
- log.info "response data chunk: <<#{d}>>"
55
+ promise.on(:data) do |d|
56
+ log.info "promise data chunk: <<#{d.size}>>"
29
57
  end
58
+ end
30
59
 
31
- puts "Sending POST request"
32
- stream.headers({
33
- ":method" => "post",
34
- ":host" => "localhost",
35
- ":path" => "/resource",
36
- "accept" => "*/*"
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
- stream.data("woot!")
94
+ while !sock.closed? && !sock.eof?
95
+ data = sock.read_nonblock(1024)
96
+ # puts "Received bytes: #{data.inspect}"
40
97
 
41
- while !sock.closed? && !sock.eof?
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
@@ -1,7 +1,12 @@
1
1
  $: << 'lib' << '../lib'
2
2
 
3
+ require 'optparse'
3
4
  require 'socket'
5
+ require 'openssl'
4
6
  require 'http/2'
7
+ require 'uri'
8
+
9
+ DRAFT = 'HTTP-draft-06/2.0'
5
10
 
6
11
  class Logger
7
12
  def initialize(id)
@@ -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-----
@@ -1,10 +1,35 @@
1
1
  require_relative 'helper'
2
2
 
3
- puts "Starting server on port 8080"
4
- Socket.tcp_server_loop(8080) do |sock|
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::Connection.new(:server)
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
- "payload size: #{buffer.size}"
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
- conn << data
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