net-http2 0.8.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ba9816c229aab7bb320437a77c3c91ed60752609
4
- data.tar.gz: 63836c93155c8d717f68542519b145f87e5e6bd1
3
+ metadata.gz: 06fbac929ced0fe89503612541dd469842084882
4
+ data.tar.gz: b1a1119b920b3e2aaa2445b4f3810a9438ee32b2
5
5
  SHA512:
6
- metadata.gz: bb589fa1c74d8fb02f79161660756667c3420baaa3810007a47506df85c5b996cfa46524d98bf6a11e2099dedc9fd2bba7922fd4ed171201dce5b59472e5012a
7
- data.tar.gz: 749b07f13a864be5a935ee7fbeef1bc912e1a4e75170f9c1334de8799aeeb0f318e43a6cc209bcc8733b6687121bee756165a759e17273e47ddc50fbeaee63ba
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
- ## Objects
48
+ # create a client
49
+ client = NetHttp2::Client.new("http://106.186.112.116")
44
50
 
45
- ### `NetHttp2::Client`
46
- To create a new client:
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
- ```ruby
49
- NetHttp2::Client.new(url)
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
- For instance:
79
+ To create a new client:
80
+ ```ruby
81
+ NetHttp2::Client.new("http://106.186.112.116")
82
+ ```
60
83
 
61
- ```ruby
62
- certificate = File.read("cert.pem")
63
- ctx = OpenSSL::SSL::SSLContext.new
64
- ctx.key = OpenSSL::PKey::RSA.new(certificate, "cert_password")
65
- ctx.cert = OpenSSL::X509::Certificate.new(certificate)
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
- NetHttp2::Client.new(url, ssl_context: ctx)
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
- For example:
84
-
85
- ```ruby
86
- response_1 = client.call(:get, '/path1')
87
- response_2 = client.call(:get, '/path2', headers: { 'x-custom' => 'custom' })
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
- > The real benefit of HTTP/2 is being able to receive body and header streams. The non-blocking API calls are currently being developed.
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
 
@@ -22,11 +22,20 @@ module NetHttp2
22
22
  end
23
23
 
24
24
  def call(method, path, options={})
25
- request = NetHttp2::Request.new(method, @uri, path, options)
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
- return if @socket_thread
59
+ @mutex.synchronize do
56
60
 
57
- socket = new_socket
61
+ return if @socket_thread
58
62
 
59
- @socket_thread = Thread.new do
63
+ socket = new_socket
60
64
 
61
- begin
62
- thread_loop(socket)
63
- ensure
64
- socket.close unless socket.closed?
65
- @socket_thread = nil
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.read_nonblock(1024)
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 && thread.alive?
156
+ return unless thread
150
157
  thread.exit
151
158
  thread.join
152
159
  end
@@ -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
@@ -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
- @block = nil
11
+ @async = false
12
12
 
13
- @h2_stream.on(:headers) do |hs|
14
- hs.each { |k, v| @headers[k] = v }
15
- end
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
- send_data_of request
23
- sync_respond(request.timeout)
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 send_data_of(request)
29
- headers = request.headers
30
- body = request.body
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(timeout)
41
- wait(timeout)
42
- response if @completed
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 wait(timeout)
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
@@ -1,3 +1,3 @@
1
1
  module NetHttp2
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
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.8.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-29 00:00:00.000000000 Z
11
+ date: 2016-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2