http-2 0.7.0 → 0.8.0

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: c7bbedd001cf6de0ec84caef4ccd0e4e818315d3
4
- data.tar.gz: c1129e49ae319b1cf31151f4e75bf16f0a108e0a
3
+ metadata.gz: cf205c4c32086ed1a2ec2d784283e7e2c2c983d2
4
+ data.tar.gz: 272d84cd37cac57b38d391480795b39661fcb20a
5
5
  SHA512:
6
- metadata.gz: 6c68277cdbc49a1fd695c177e1015334946e4cbba9e93cc2b7b93572b45ab89c61e92ed2638f485c1eafaa2909f1d8a8d0c3ae352743697176b37bf1b27e8be3
7
- data.tar.gz: 3357aafb0da7559f22230e8503bd025b4c993ce0fc1dbcc40d078b6b6d0bb5b98b831f40b0d9f53c0358b42939dc7697b72b319ce183975eaaea6e95c092b96a
6
+ metadata.gz: 338b39a100523327a407abec0cf68586e079290626661e8d39c803dbee7eee1b958d3f70bdcbfb0712d7fd36db0cb315cb729a1704ce8b655485c7b02a2108fb
7
+ data.tar.gz: d34d0a7325076767c9c36a85319c733268a812968482ff4c81be7533b7467f0a7abfb99ad498bd707dd93a706438ba709b2356554aaa6a34af768290524599b9
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  autotest
2
2
  --color
3
3
  --format documentation
4
+ --order rand
@@ -0,0 +1,18 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ DisplayCopNames: true
5
+ Exclude:
6
+ - 'bin/*'
7
+
8
+ Lint/EndAlignment:
9
+ AlignWith: variable
10
+
11
+ Style/CaseIndentation:
12
+ IndentWhenRelativeTo: end
13
+
14
+ Style/IndentHash:
15
+ EnforcedStyle: consistent
16
+
17
+ Style/TrailingComma:
18
+ EnforcedStyleForMultiline: comma
@@ -0,0 +1,46 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2015-04-06 10:04:21 -0700 using RuboCop version 0.30.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 23
9
+ Metrics/AbcSize:
10
+ Max: 186
11
+
12
+ # Offense count: 16
13
+ Metrics/BlockNesting:
14
+ Max: 5
15
+
16
+ # Offense count: 7
17
+ # Configuration parameters: CountComments.
18
+ Metrics/ClassLength:
19
+ Max: 331
20
+
21
+ # Offense count: 12
22
+ Metrics/CyclomaticComplexity:
23
+ Max: 60
24
+
25
+ # Offense count: 349
26
+ # Configuration parameters: AllowURI, URISchemes.
27
+ Metrics/LineLength:
28
+ Max: 231
29
+
30
+ # Offense count: 27
31
+ # Configuration parameters: CountComments.
32
+ Metrics/MethodLength:
33
+ Max: 134
34
+
35
+ # Offense count: 1
36
+ # Configuration parameters: CountKeywordArgs.
37
+ Metrics/ParameterLists:
38
+ Max: 6
39
+
40
+ # Offense count: 10
41
+ Metrics/PerceivedComplexity:
42
+ Max: 46
43
+
44
+ # Offense count: 7
45
+ Style/Documentation:
46
+ Enabled: false
@@ -1,4 +1,12 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
- - 2.0.0
4
- script: bundle exec rake test
4
+ - 2.1
5
+ - 2.2
6
+ - jruby-9.0.0.0
7
+ - jruby-head
8
+ - rbx-2
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: jruby-head
12
+ - rvm: rbx-2
data/Gemfile CHANGED
@@ -4,13 +4,15 @@ gem 'rake'
4
4
  gem 'yard'
5
5
 
6
6
  group :test do
7
- gem 'coveralls', :require => false
8
- gem 'rspec'
9
- gem 'rspec-autotest'
10
- gem 'autotest-standalone'
7
+ gem 'activesupport'
11
8
  gem 'autotest-growl'
9
+ gem 'autotest-standalone'
10
+ gem 'coveralls', require: false
12
11
  gem 'pry'
13
- gem 'pry-byebug'
12
+ gem 'pry-byebug', platform: :mri
13
+ gem 'rspec'
14
+ gem 'rspec-autotest'
15
+ gem 'rubocop', '~> 0.30.0'
14
16
  end
15
17
 
16
18
  gemspec
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage Status](https://coveralls.io/repos/igrigorik/http-2/badge.png)](https://coveralls.io/r/igrigorik/http-2)
6
6
  [![Analytics](https://ga-beacon.appspot.com/UA-71196-10/http-2/readme)](https://github.com/igrigorik/ga-beacon)
7
7
 
8
- Pure ruby, framework and transport agnostic implementation of [HTTP 2.0 protocol](http://tools.ietf.org/html/draft-ietf-httpbis-http2) with support for:
8
+ Pure Ruby, framework and transport agnostic, implementation of HTTP/2 protocol and HPACK header compression with support for:
9
9
 
10
10
  * [Binary framing](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_binary_framing_layer) parsing and encoding
11
11
  * [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)
@@ -14,12 +14,10 @@ Pure ruby, framework and transport agnostic implementation of [HTTP 2.0 protocol
14
14
  * Connection and stream management
15
15
  * And more... see [API docs](http://www.rubydoc.info/github/igrigorik/http-2/frames)
16
16
 
17
- Current implementation (see [HPBN chapter for HTTP 2.0 overview](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html)), is based on:
17
+ Protocol specifications:
18
18
 
19
- * [draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14)
20
- * [draft-ietf-httpbis-header-compression-09](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09)
21
-
22
- _Note: the underlying specifications are still evolving, expect APIs to change and evolve also..._
19
+ * [Hypertext Transfer Protocol Version 2 (RFC 7540)](https://httpwg.github.io/specs/rfc7540.html)
20
+ * [HPACK: Header Compression for HTTP/2 (RFC 7541)](https://httpwg.github.io/specs/rfc7541.html)
23
21
 
24
22
 
25
23
  ## Getting started
@@ -30,7 +28,7 @@ $> gem install http-2
30
28
 
31
29
  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.
32
30
 
33
- 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:
31
+ Your code is responsible for feeding data into the parser, which performs all of the necessary HTTP/2 decoding, state management and the rest, and vice versa, the parser will emit bytes (encoded HTTP/2 frames) that you can then route to the destination. Roughly, this works as follows:
34
32
 
35
33
  ```ruby
36
34
  require 'http/2'
@@ -56,7 +54,7 @@ Depending on the role of the endpoint you must initialize either a [Client](http
56
54
  server = HTTP2::Server.new
57
55
 
58
56
  server.on(:stream) { |stream| ... } # process inbound stream
59
- server.on(:frame) { |bytes| ... } # encoded HTTP 2.0 frames
57
+ server.on(:frame) { |bytes| ... } # encoded HTTP/2 frames
60
58
 
61
59
  server.ping { ... } # run liveness check, process pong response
62
60
  server.goaway # send goaway frame to the client
@@ -83,42 +81,42 @@ Events emitted by the connection object:
83
81
  </tr>
84
82
  <tr>
85
83
  <td><b>:frame</b></td>
86
- <td>fires once for every encoded HTTP 2.0 frame that needs to be sent to the peer</td>
84
+ <td>fires once for every encoded HTTP/2 frame that needs to be sent to the peer</td>
87
85
  </tr>
88
86
  </table>
89
87
 
90
88
 
91
89
  ### Stream lifecycle management
92
90
 
93
- A single HTTP 2.0 connection can [multiplex multiple streams](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#REQUEST_RESPONSE_MULTIPLEXING) in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
91
+ A single HTTP/2 connection can [multiplex multiple streams](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#REQUEST_RESPONSE_MULTIPLEXING) in parallel: multiple requests and responses can be in flight simultaneously and stream data can be interleaved and prioritized. Further, the specification provides a well-defined lifecycle for each stream (see below).
94
92
 
95
93
  The good news is, all of the stream management, and state transitions, and error checking is handled by the library. All you have to do is subscribe to appropriate events (marked with ":" prefix in diagram below) and provide your application logic to handle request and response processing.
96
94
 
97
95
  ```
98
- +--------+
99
- PP | | PP
100
- ,--------| idle |--------.
101
- / | | \
102
- v +--------+ v
103
- +----------+ | +----------+
104
- | | | H | |
105
- ,---|:reserved | | |:reserved |---.
106
- | | (local) | v | (remote) | |
107
- | +----------+ +--------+ +----------+ |
108
- | | :active | | :active | |
109
- | | ,-------|:active |-------. | |
110
- | | H / ES | | ES \ H | |
111
- | v v +--------+ v v |
112
- | +-----------+ | +-_---------+ |
113
- | |:half_close| | |:half_close| |
114
- | | (remote) | | | (local) | |
115
- | +-----------+ | +-----------+ |
116
- | | v | |
117
- | | ES/R +--------+ ES/R | |
118
- | `----------->| |<-----------' |
119
- | R | :close | R |
120
- `-------------------->| |<--------------------'
121
- +--------+
96
+ +--------+
97
+ PP | | PP
98
+ ,--------| idle |--------.
99
+ / | | \
100
+ v +--------+ v
101
+ +----------+ | +----------+
102
+ | | | H | |
103
+ ,---|:reserved | | |:reserved |---.
104
+ | | (local) | v | (remote) | |
105
+ | +----------+ +--------+ +----------+ |
106
+ | | :active | | :active | |
107
+ | | ,-------|:active |-------. | |
108
+ | | H / ES | | ES \ H | |
109
+ | v v +--------+ v v |
110
+ | +-----------+ | +-----------+ |
111
+ | |:half_close| | |:half_close| |
112
+ | | (remote) | | | (local) | |
113
+ | +-----------+ | +-----------+ |
114
+ | | v | |
115
+ | | ES/R +--------+ ES/R | |
116
+ | `----------->| |<-----------' |
117
+ | R | :close | R |
118
+ `-------------------->| |<--------------------'
119
+ +--------+
122
120
  ```
123
121
 
124
122
  For sake of example, let's take a look at a simple server implementation:
@@ -188,7 +186,7 @@ Events emitted by the [Stream object](http://www.rubydoc.info/github/igrigorik/h
188
186
 
189
187
  ### Prioritization
190
188
 
191
- 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:
189
+ Each HTTP/2 [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:
192
190
 
193
191
  ```ruby
194
192
  client = HTTP2::Client.new
@@ -205,7 +203,7 @@ On the opposite side, the server can optimize its stream processing order or res
205
203
 
206
204
  ### Flow control
207
205
 
208
- Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP 2.0 provides a simple mechanism for [stream and connection flow control](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_flow_control).
206
+ Multiplexing multiple streams over the same TCP connection introduces contention for shared bandwidth resources. Stream priorities can help determine the relative order of delivery, but priorities alone are insufficient to control how the resource allocation is performed between multiple streams. To address this, HTTP/2 provides a simple mechanism for [stream and connection flow control](http://chimera.labs.oreilly.com/books/1230000000545/ch12.html#_flow_control).
209
207
 
210
208
  Connection and stream flow control is handled by the library: all streams are initialized with the default window size (64KB), and send/receive window updates are automatically processed - i.e. window is decremented on outgoing data transfers, and incremented on receipt of window frames. Similarly, if the window is exceeded, then data frames are automatically buffered until window is updated.
211
209
 
@@ -230,7 +228,7 @@ conn.settings(streams: 100, window: Float::INFINITY)
230
228
 
231
229
  ### Server push
232
230
 
233
- 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:
231
+ An HTTP/2 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:
234
232
 
235
233
  ```ruby
236
234
  conn = HTTP2::Server.new
data/Rakefile CHANGED
@@ -1,12 +1,11 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require_relative "lib/tasks/generate_huffman_table"
4
-
5
- desc "Run all RSpec tests"
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task :default => :spec
9
- task :test => [:spec]
10
-
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
11
4
  require 'yard'
5
+ require_relative 'lib/tasks/generate_huffman_table'
6
+
7
+ RSpec::Core::RakeTask.new
8
+ RuboCop::RakeTask.new
12
9
  YARD::Rake::YardocTask.new
10
+
11
+ task default: [:spec, :rubocop]
@@ -30,19 +30,6 @@ $> ruby server.rb
30
30
  $> nghttp -vnu http://localhost:8080 # Direct request to Ruby server
31
31
  ```
32
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
33
  ### Twitter (Java server)
47
34
 
48
35
  ```bash
@@ -2,14 +2,13 @@ require_relative 'helper'
2
2
 
3
3
  options = {}
4
4
  OptionParser.new do |opts|
5
- opts.banner = "Usage: client.rb [options]"
5
+ opts.banner = 'Usage: client.rb [options]'
6
6
 
7
- opts.on("-d", "--data [String]", "HTTP payload") do |v|
7
+ opts.on('-d', '--data [String]', 'HTTP payload') do |v|
8
8
  options[:payload] = v
9
9
  end
10
10
  end.parse!
11
11
 
12
-
13
12
  uri = URI.parse(ARGV[0] || 'http://localhost:8080/')
14
13
  tcp = TCPSocket.new(uri.host, uri.port)
15
14
  sock = nil
@@ -68,19 +67,18 @@ conn.on(:altsvc) do |f|
68
67
  end
69
68
 
70
69
  stream.on(:close) do
71
- log.info "stream closed"
70
+ log.info 'stream closed'
72
71
  sock.close
73
72
  end
74
73
 
75
74
  stream.on(:half_close) do
76
- log.info "closing client-end of the stream"
75
+ log.info 'closing client-end of the stream'
77
76
  end
78
77
 
79
78
  stream.on(:headers) do |h|
80
79
  log.info "response headers: #{h}"
81
80
  end
82
81
 
83
-
84
82
  stream.on(:data) do |d|
85
83
  log.info "response data chunk: <<#{d}>>"
86
84
  end
@@ -89,17 +87,16 @@ stream.on(:altsvc) do |f|
89
87
  log.info "received ALTSVC #{f}"
90
88
  end
91
89
 
92
-
93
90
  head = {
94
- ":scheme" => uri.scheme,
95
- ":method" => (options[:payload].nil? ? "GET" : "POST"),
96
- ":authority" => [uri.host, uri.port].join(':'),
97
- ":path" => uri.path,
98
- "accept" => "*/*"
91
+ ':scheme' => uri.scheme,
92
+ ':method' => (options[:payload].nil? ? 'GET' : 'POST'),
93
+ ':authority' => [uri.host, uri.port].join(':'),
94
+ ':path' => uri.path,
95
+ 'accept' => '*/*',
99
96
  }
100
97
 
101
- puts "Sending HTTP 2.0 request"
102
- if head[":method"] == "GET"
98
+ puts 'Sending HTTP 2.0 request'
99
+ if head[':method'] == 'GET'
103
100
  stream.headers(head, end_stream: true)
104
101
  else
105
102
  stream.headers(head, end_stream: false)
@@ -112,7 +109,7 @@ while !sock.closed? && !sock.eof?
112
109
 
113
110
  begin
114
111
  conn << data
115
- rescue Exception => e
112
+ rescue => e
116
113
  puts "Exception: #{e}, #{e.message} - closing socket."
117
114
  sock.close
118
115
  end
@@ -1,4 +1,4 @@
1
- $: << 'lib' << '../lib'
1
+ $LOAD_PATH << 'lib' << '../lib'
2
2
 
3
3
  require 'optparse'
4
4
  require 'socket'
@@ -6,7 +6,7 @@ require 'openssl'
6
6
  require 'http/2'
7
7
  require 'uri'
8
8
 
9
- DRAFT = 'h2-14'
9
+ DRAFT = 'h2'
10
10
 
11
11
  class Logger
12
12
  def initialize(id)
@@ -1,14 +1,14 @@
1
1
  require_relative 'helper'
2
2
 
3
- options = {port: 8080}
3
+ options = { port: 8080 }
4
4
  OptionParser.new do |opts|
5
- opts.banner = "Usage: server.rb [options]"
5
+ opts.banner = 'Usage: server.rb [options]'
6
6
 
7
- opts.on("-s", "--secure", "HTTPS mode") do |v|
7
+ opts.on('-s', '--secure', 'HTTPS mode') do |v|
8
8
  options[:secure] = v
9
9
  end
10
10
 
11
- opts.on("-p", "--port [Integer]", "listen port") do |v|
11
+ opts.on('-p', '--port [Integer]', 'listen port') do |v|
12
12
  options[:port] = v
13
13
  end
14
14
  end.parse!
@@ -18,8 +18,8 @@ server = TCPServer.new(options[:port])
18
18
 
19
19
  if options[:secure]
20
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"))
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
23
  ctx.npn_protocols = [DRAFT]
24
24
 
25
25
  server = OpenSSL::SSL::SSLServer.new(server, ctx)
@@ -27,7 +27,7 @@ end
27
27
 
28
28
  loop do
29
29
  sock = server.accept
30
- puts "New TCP connection!"
30
+ puts 'New TCP connection!'
31
31
 
32
32
  conn = HTTP2::Server.new
33
33
  conn.on(:frame) do |bytes|
@@ -45,8 +45,8 @@ loop do
45
45
  log = Logger.new(stream.id)
46
46
  req, buffer = {}, ''
47
47
 
48
- stream.on(:active) { log.info "cliend opened new stream" }
49
- stream.on(:close) { log.info "stream closed" }
48
+ stream.on(:active) { log.info 'cliend opened new stream' }
49
+ stream.on(:close) { log.info 'stream closed' }
50
50
 
51
51
  stream.on(:headers) do |h|
52
52
  req = Hash[*h.flatten]
@@ -59,36 +59,36 @@ loop do
59
59
  end
60
60
 
61
61
  stream.on(:half_close) do
62
- log.info "client closed its end of the stream"
62
+ log.info 'client closed its end of the stream'
63
63
 
64
64
  response = nil
65
- if req[":method"] == "POST"
65
+ if req[':method'] == 'POST'
66
66
  log.info "Received POST request, payload: #{buffer}"
67
67
  response = "Hello HTTP 2.0! POST payload: #{buffer}"
68
68
  else
69
- log.info "Received GET request"
70
- response = "Hello HTTP 2.0! GET request"
69
+ log.info 'Received GET request'
70
+ response = 'Hello HTTP 2.0! GET request'
71
71
  end
72
72
 
73
73
  stream.headers({
74
- ":status" => "200",
75
- "content-length" => response.bytesize.to_s,
76
- "content-type" => "text/plain"
74
+ ':status' => '200',
75
+ 'content-length' => response.bytesize.to_s,
76
+ 'content-type' => 'text/plain',
77
77
  }, end_stream: false)
78
78
 
79
79
  # split response into multiple DATA frames
80
- stream.data(response.slice!(0,5), end_stream: false)
80
+ stream.data(response.slice!(0, 5), end_stream: false)
81
81
  stream.data(response)
82
82
  end
83
83
  end
84
84
 
85
- while !sock.closed? && !sock.eof?
85
+ while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
86
86
  data = sock.readpartial(1024)
87
87
  # puts "Received bytes: #{data.unpack("H*").first}"
88
88
 
89
89
  begin
90
90
  conn << data
91
- rescue Exception => e
91
+ rescue => e
92
92
  puts "Exception: #{e}, #{e.message} - closing socket."
93
93
  sock.close
94
94
  end