net-http2 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +100 -23
- data/lib/net-http2/client.rb +23 -16
- data/lib/net-http2/request.rb +14 -1
- data/lib/net-http2/stream.rb +61 -25
- data/lib/net-http2/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06fbac929ced0fe89503612541dd469842084882
|
4
|
+
data.tar.gz: b1a1119b920b3e2aaa2445b4f3810a9438ee32b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a49a1d2f16b33d0260464584880d74e55076d4b7c1f59545f9fabb6af2861c8c357596f1fd16ab30b4212450be0ea242f2bcf2ea76c6a966d8d9f38762f73f4
|
7
|
+
data.tar.gz: 1d7492113575170952373c2326c13bab44018f1ae941f71a1ff56eeb6e4426ef9721499c6b88a12febda67fb73c1b93d65eb76ec7bada382b0650fa7b2aa49d4
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
[![Build Status](https://travis-ci.org/ostinelli/net-http2.svg?branch=master)](https://travis-ci.org/ostinelli/net-http2)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/ostinelli/net-http2/badges/gpa.svg)](https://codeclimate.com/github/ostinelli/net-http2)
|
2
3
|
|
3
4
|
# NetHttp2
|
4
5
|
|
5
6
|
NetHttp2 is an HTTP/2 client for Ruby.
|
6
7
|
|
7
|
-
|
8
8
|
## Installation
|
9
9
|
Just install the gem:
|
10
10
|
|
@@ -19,7 +19,9 @@ gem 'net-http2'
|
|
19
19
|
```
|
20
20
|
|
21
21
|
## Usage
|
22
|
+
NetHttp2 can perform sync and async calls. Sync calls are very similar to the HTTP/1 calls, while async calls take advantage of the streaming properties of HTTP/2.
|
22
23
|
|
24
|
+
To perform a sync call:
|
23
25
|
```ruby
|
24
26
|
require 'net-http2'
|
25
27
|
|
@@ -27,7 +29,7 @@ require 'net-http2'
|
|
27
29
|
client = NetHttp2::Client.new("http://106.186.112.116")
|
28
30
|
|
29
31
|
# send request
|
30
|
-
response = client.get
|
32
|
+
response = client.call(:get, '/')
|
31
33
|
|
32
34
|
# read the response
|
33
35
|
response.ok? # => true
|
@@ -39,16 +41,34 @@ response.body # => "A body"
|
|
39
41
|
client.close
|
40
42
|
```
|
41
43
|
|
44
|
+
To perform an async call:
|
45
|
+
```ruby
|
46
|
+
require 'net-http2'
|
42
47
|
|
43
|
-
|
48
|
+
# create a client
|
49
|
+
client = NetHttp2::Client.new("http://106.186.112.116")
|
44
50
|
|
45
|
-
|
46
|
-
|
51
|
+
# prepare request
|
52
|
+
request = client.prepare_request(:get, '/')
|
53
|
+
request.on(:headers) { |headers| p headers }
|
54
|
+
request.on(:body_chunk) { |chunk| p chunk }
|
55
|
+
request.on(:close) { puts "request completed!" }
|
47
56
|
|
48
|
-
|
49
|
-
|
57
|
+
# send
|
58
|
+
client.call_async(request)
|
59
|
+
|
60
|
+
# We need to wait for the callbacks to be triggered, so here's a quick and dirty fix for this example.
|
61
|
+
# In real life, if you are using async requests so you probably have a running loop that keeps your program alive.
|
62
|
+
sleep 5
|
63
|
+
|
64
|
+
# close the connection
|
65
|
+
client.close
|
50
66
|
```
|
51
67
|
|
68
|
+
## Objects
|
69
|
+
|
70
|
+
### `NetHttp2::Client`
|
71
|
+
|
52
72
|
#### Methods
|
53
73
|
|
54
74
|
* **new(url, options={})** → **`NetHttp2::Client`**
|
@@ -56,16 +76,20 @@ NetHttp2::Client.new(url)
|
|
56
76
|
Returns a new client. `url` is a `string` such as https://localhost:443.
|
57
77
|
The only current option is `:ssl_context`, in case the url has an https scheme and you want your SSL client to use a custom context.
|
58
78
|
|
59
|
-
|
79
|
+
To create a new client:
|
80
|
+
```ruby
|
81
|
+
NetHttp2::Client.new("http://106.186.112.116")
|
82
|
+
```
|
60
83
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
84
|
+
To create a new client with a custom SSL context:
|
85
|
+
```ruby
|
86
|
+
certificate = File.read("cert.pem")
|
87
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
88
|
+
ctx.key = OpenSSL::PKey::RSA.new(certificate, "cert_password")
|
89
|
+
ctx.cert = OpenSSL::X509::Certificate.new(certificate)
|
66
90
|
|
67
|
-
|
68
|
-
|
91
|
+
NetHttp2::Client.new("http://106.186.112.116", ssl_context: ctx)
|
92
|
+
```
|
69
93
|
|
70
94
|
* **uri** → **`URI`**
|
71
95
|
|
@@ -80,18 +104,68 @@ These behave similarly to HTTP/1 calls.
|
|
80
104
|
|
81
105
|
`method` is a symbol that specifies the `:method` header (`:get`, `:post`, `:put`, `:patch`, `:delete`, `:options`). The body and the headers of the request can be specified in the options, together with the timeout.
|
82
106
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
response_3 = client.call(:post '/path3', body: "the request body", timeout: 1)
|
89
|
-
```
|
107
|
+
```ruby
|
108
|
+
response_1 = client.call(:get, '/path1')
|
109
|
+
response_2 = client.call(:get, '/path2', headers: { 'x-custom' => 'custom' })
|
110
|
+
response_3 = client.call(:post '/path3', body: "the request body", timeout: 1)
|
111
|
+
```
|
90
112
|
|
91
113
|
|
92
114
|
##### Non-blocking calls
|
115
|
+
The real benefit of HTTP/2 is being able to receive body and header streams. Instead of buffering the whole response, you might want to react immediately upon receiving those streams. This is what non-blocking calls are for.
|
116
|
+
|
117
|
+
* **prepare_request(method, path, options={})** → **`NetHttp2::Request`**
|
118
|
+
|
119
|
+
Prepares an async request. Arguments are the same as the `call` method, with the difference that the `:timeout` option will be ignored. In an async call, you will need to write your own logic for timeouts.
|
93
120
|
|
94
|
-
|
121
|
+
```ruby
|
122
|
+
request = client.prepare_request(:get, '/path', headers: { 'x-custom-header' => 'custom' })
|
123
|
+
```
|
124
|
+
|
125
|
+
* **on(event, &block)**
|
126
|
+
|
127
|
+
Allows to set a callback for the request. Available events are:
|
128
|
+
|
129
|
+
* `:headers`: triggered when headers are received (called once).
|
130
|
+
* `:body_chunk`: triggered when body chunks are received (may be called multiple times).
|
131
|
+
* `:close`: triggered when the request has been completed (called once).
|
132
|
+
|
133
|
+
Even if NetHttp2 is thread-safe, the async callbacks will be executed in a different thread, so ensure that your code in the callbacks is thread-safe.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
request.on(:headers) { |headers| p headers }
|
137
|
+
request.on(:body_chunk) { |chunk| p chunk }
|
138
|
+
request.on(:close) { puts "request completed!" }
|
139
|
+
```
|
140
|
+
|
141
|
+
* **call_async(request)**
|
142
|
+
|
143
|
+
Calls the server with the async request.
|
144
|
+
|
145
|
+
|
146
|
+
### `NetHttp2::Request`
|
147
|
+
|
148
|
+
#### Methods
|
149
|
+
|
150
|
+
* **method** → **`symbol`**
|
151
|
+
|
152
|
+
The request's method.
|
153
|
+
|
154
|
+
* **uri** → **`URI`**
|
155
|
+
|
156
|
+
The request's URI.
|
157
|
+
|
158
|
+
* **path** → **`string`**
|
159
|
+
|
160
|
+
The request's path.
|
161
|
+
|
162
|
+
* **body** → **`string`**
|
163
|
+
|
164
|
+
The request's body.
|
165
|
+
|
166
|
+
* **timeout** → **`integer`**
|
167
|
+
|
168
|
+
The request's timeout.
|
95
169
|
|
96
170
|
|
97
171
|
### `NetHttp2::Response`
|
@@ -115,6 +189,9 @@ These behave similarly to HTTP/1 calls.
|
|
115
189
|
Returns the RAW body of the response.
|
116
190
|
|
117
191
|
|
192
|
+
## Thread-Safety
|
193
|
+
NetHttp2 is thread-safe.
|
194
|
+
|
118
195
|
## Contributing
|
119
196
|
So you want to contribute? That's great! Please follow the guidelines below. It will make it easier to get merged in.
|
120
197
|
|
data/lib/net-http2/client.rb
CHANGED
@@ -22,11 +22,20 @@ module NetHttp2
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def call(method, path, options={})
|
25
|
-
request =
|
25
|
+
request = prepare_request(method, path, options)
|
26
26
|
ensure_open
|
27
27
|
new_stream.call_with request
|
28
28
|
end
|
29
29
|
|
30
|
+
def call_async(request)
|
31
|
+
ensure_open
|
32
|
+
new_stream.async_call_with request
|
33
|
+
end
|
34
|
+
|
35
|
+
def prepare_request(method, path, options={})
|
36
|
+
NetHttp2::Request.new(method, @uri, path, options)
|
37
|
+
end
|
38
|
+
|
30
39
|
def ssl?
|
31
40
|
@is_ssl
|
32
41
|
end
|
@@ -42,27 +51,25 @@ module NetHttp2
|
|
42
51
|
|
43
52
|
private
|
44
53
|
|
45
|
-
def async_call_with(request, &block)
|
46
|
-
ensure_open
|
47
|
-
new_stream.async_call_with request, &block
|
48
|
-
end
|
49
|
-
|
50
54
|
def new_stream
|
51
55
|
NetHttp2::Stream.new(uri: @uri, h2_stream: h2.new_stream)
|
52
56
|
end
|
53
57
|
|
54
58
|
def ensure_open
|
55
|
-
|
59
|
+
@mutex.synchronize do
|
56
60
|
|
57
|
-
|
61
|
+
return if @socket_thread
|
58
62
|
|
59
|
-
|
63
|
+
socket = new_socket
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
@socket_thread = Thread.new do
|
66
|
+
|
67
|
+
begin
|
68
|
+
thread_loop(socket)
|
69
|
+
ensure
|
70
|
+
socket.close unless socket.closed?
|
71
|
+
@socket_thread = nil
|
72
|
+
end
|
66
73
|
end
|
67
74
|
end
|
68
75
|
end
|
@@ -83,7 +90,7 @@ module NetHttp2
|
|
83
90
|
end
|
84
91
|
|
85
92
|
if ready[0].include?(@pipe_r)
|
86
|
-
data_to_send = @pipe_r.
|
93
|
+
data_to_send = @pipe_r.readpartial(1024)
|
87
94
|
socket.write(data_to_send)
|
88
95
|
end
|
89
96
|
end
|
@@ -146,7 +153,7 @@ module NetHttp2
|
|
146
153
|
end
|
147
154
|
|
148
155
|
def exit_thread(thread)
|
149
|
-
return unless thread
|
156
|
+
return unless thread
|
150
157
|
thread.exit
|
151
158
|
thread.join
|
152
159
|
end
|
data/lib/net-http2/request.rb
CHANGED
@@ -13,6 +13,8 @@ module NetHttp2
|
|
13
13
|
@body = options[:body]
|
14
14
|
@headers = options[:headers] || {}
|
15
15
|
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
16
|
+
|
17
|
+
@events = {}
|
16
18
|
end
|
17
19
|
|
18
20
|
def headers
|
@@ -30,8 +32,19 @@ module NetHttp2
|
|
30
32
|
@headers.delete('content-length')
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
35
|
@headers
|
35
36
|
end
|
37
|
+
|
38
|
+
def on(event, &block)
|
39
|
+
raise ArgumentError, 'on event must provide a block' unless block_given?
|
40
|
+
|
41
|
+
@events[event] ||= []
|
42
|
+
@events[event] << block
|
43
|
+
end
|
44
|
+
|
45
|
+
def emit(event, arg)
|
46
|
+
return unless @events[event]
|
47
|
+
@events[event].each { |b| b.call(arg) }
|
48
|
+
end
|
36
49
|
end
|
37
50
|
end
|
data/lib/net-http2/stream.rb
CHANGED
@@ -4,30 +4,72 @@ module NetHttp2
|
|
4
4
|
|
5
5
|
def initialize(options={})
|
6
6
|
@h2_stream = options[:h2_stream]
|
7
|
-
@uri = options[:uri]
|
8
7
|
@headers = {}
|
9
8
|
@data = ''
|
9
|
+
@request = nil
|
10
10
|
@completed = false
|
11
|
-
@
|
11
|
+
@async = false
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@h2_stream.on(:data) { |d| @data << d }
|
18
|
-
@h2_stream.on(:close) { @completed = true }
|
13
|
+
listen_for_headers
|
14
|
+
listen_for_data
|
15
|
+
listen_for_close
|
19
16
|
end
|
20
17
|
|
21
18
|
def call_with(request)
|
22
|
-
|
23
|
-
|
19
|
+
@request = request
|
20
|
+
send_request_data
|
21
|
+
sync_respond
|
22
|
+
end
|
23
|
+
|
24
|
+
def async_call_with(request)
|
25
|
+
@request = request
|
26
|
+
@async = true
|
27
|
+
send_request_data
|
28
|
+
end
|
29
|
+
|
30
|
+
def completed?
|
31
|
+
@completed
|
32
|
+
end
|
33
|
+
|
34
|
+
def async?
|
35
|
+
@async
|
24
36
|
end
|
25
37
|
|
26
38
|
private
|
27
39
|
|
28
|
-
def
|
29
|
-
headers
|
30
|
-
|
40
|
+
def listen_for_headers
|
41
|
+
@h2_stream.on(:headers) do |hs_array|
|
42
|
+
hs = Hash[*hs_array.flatten]
|
43
|
+
|
44
|
+
if async?
|
45
|
+
@request.emit(:headers, hs)
|
46
|
+
else
|
47
|
+
@headers.merge!(hs)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def listen_for_data
|
53
|
+
@h2_stream.on(:data) do |data|
|
54
|
+
if async?
|
55
|
+
@request.emit(:body_chunk, data)
|
56
|
+
else
|
57
|
+
@data << data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def listen_for_close
|
63
|
+
@h2_stream.on(:close) do |data|
|
64
|
+
@completed = true
|
65
|
+
|
66
|
+
@request.emit(:close, data) if async?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_request_data
|
71
|
+
headers = @request.headers
|
72
|
+
body = @request.body
|
31
73
|
|
32
74
|
if body
|
33
75
|
@h2_stream.headers(headers, end_stream: false)
|
@@ -37,24 +79,18 @@ module NetHttp2
|
|
37
79
|
end
|
38
80
|
end
|
39
81
|
|
40
|
-
def sync_respond
|
41
|
-
|
42
|
-
|
82
|
+
def sync_respond
|
83
|
+
wait_for_completed
|
84
|
+
|
85
|
+
NetHttp2::Response.new(headers: @headers, body: @data) if @completed
|
43
86
|
end
|
44
87
|
|
45
|
-
def
|
46
|
-
cutoff_time = Time.now + timeout
|
88
|
+
def wait_for_completed
|
89
|
+
cutoff_time = Time.now + @request.timeout
|
47
90
|
|
48
91
|
while !@completed && Time.now < cutoff_time
|
49
92
|
sleep 0.1
|
50
93
|
end
|
51
94
|
end
|
52
|
-
|
53
|
-
def response
|
54
|
-
NetHttp2::Response.new(
|
55
|
-
headers: @headers,
|
56
|
-
body: @data
|
57
|
-
)
|
58
|
-
end
|
59
95
|
end
|
60
96
|
end
|
data/lib/net-http2/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-http2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roberto Ostinelli
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2
|