net-http2 0.14.0 → 0.14.1

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: dfc359f7833ccb7f0f42ae71bdf7b870e2c7b2f1
4
- data.tar.gz: 3b8fa8c3f7721edd104b271c7f85f3b090935543
3
+ metadata.gz: 7ca19f3b01ed565929287e4db629d7c5a3c1522e
4
+ data.tar.gz: d31dc0fc2f7cf652dcc8bdac6fcc8b31b6721797
5
5
  SHA512:
6
- metadata.gz: 61752118de4cc018356527584ab1eb27b8e7270b531e01db85d05d78a93dca4d8c4f21161a22d1e2a56730aaeabb11754bbe78c7168f66777e0c97ee08e08db2
7
- data.tar.gz: 922f803b411e0ae38b07247cf69062f46c50f9a8ece19b8960df32bb468672375cb8cefa4948d0948c03f19fdde070f8a65cdf73cef46a3ef0606c9590674bb0
6
+ metadata.gz: b0b94a8d7e38e0fed916585ff63b8f1686e0cf0ae0107dced66a7de8199155ac62484366fd1808ba2bfeb5e6fc02fb2bcd4cabab061ecfa2833a6bf1621ef22b
7
+ data.tar.gz: f88d758b5174c933832c003ce996f0a97277bf0b24da7ba517825bcb2eb4a898b8773f56b008fe256eb247ca0a659117a8cc04be0f22f463145e60fb414ab117
data/README.md CHANGED
@@ -75,9 +75,10 @@ client.close
75
75
 
76
76
  Returns a new client. `url` is a `string` such as `http://nghttp2.org`.
77
77
  The current options are:
78
-
78
+
79
79
  * `:connect_timeout`, specifies the max connect timeout in seconds (defaults to 60).
80
80
  * `:ssl_context`, in case the url has an https scheme and you want your SSL client to use a custom context.
81
+ * `:proxy_addr`, `:proxy_port`, `:proxy_user`, `:proxy_pass`, specify Proxy connection parameters.
81
82
 
82
83
  To create a new client:
83
84
  ```ruby
@@ -105,12 +106,13 @@ These behave similarly to HTTP/1 calls.
105
106
 
106
107
  Sends a request. Returns `nil` in case a timeout occurs.
107
108
 
108
- `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.
109
+ `method` is a symbol that specifies the `:method` header (`:get`, `:post`, `:put`, `:patch`, `:delete`, `:options`). The body, headers and query-string params of the request can be specified in the options, together with the timeout.
109
110
 
110
111
  ```ruby
111
112
  response_1 = client.call(:get, '/path1')
112
113
  response_2 = client.call(:get, '/path2', headers: { 'x-custom' => 'custom' })
113
114
  response_3 = client.call(:post, '/path3', body: "the request body", timeout: 1)
115
+ response_3 = client.call(:post, '/path4', params: { page: 4 })
114
116
  ```
115
117
 
116
118
  ##### Non-blocking calls
@@ -149,6 +151,10 @@ The real benefit of HTTP/2 is being able to receive body and header streams. Ins
149
151
 
150
152
  The request's path.
151
153
 
154
+ * **params** → **`hash`**
155
+
156
+ The query string params in hash format, for example `{one: 1, two: 2}`. These will be encoded and appended to `path`.
157
+
152
158
  * **body** → **`string`**
153
159
 
154
160
  The request's body.
@@ -5,7 +5,8 @@ require 'http/2'
5
5
 
6
6
  module NetHttp2
7
7
 
8
- DRAFT = 'h2'
8
+ DRAFT = 'h2'
9
+ PROXY_SETTINGS_KEYS = [:proxy_addr, :proxy_port, :proxy_user, :proxy_pass]
9
10
 
10
11
  class Client
11
12
  attr_reader :uri
@@ -15,6 +16,10 @@ module NetHttp2
15
16
  @connect_timeout = options[:connect_timeout] || 60
16
17
  @ssl_context = add_npn_to_context(options[:ssl_context] || OpenSSL::SSL::SSLContext.new)
17
18
 
19
+ PROXY_SETTINGS_KEYS.each do |key|
20
+ instance_variable_set("@#{key}", options[key]) if options[key]
21
+ end
22
+
18
23
  @is_ssl = (@uri.scheme == 'https')
19
24
 
20
25
  @mutex = Mutex.new
@@ -55,13 +60,15 @@ module NetHttp2
55
60
  private
56
61
 
57
62
  def init_vars
58
- @socket.close if @socket && !@socket.closed?
63
+ @mutex.synchronize do
64
+ @socket.close if @socket && !@socket.closed?
59
65
 
60
- @h2 = nil
61
- @socket = nil
62
- @socket_thread = nil
63
- @first_data_sent = false
64
- @streams = {}
66
+ @h2 = nil
67
+ @socket = nil
68
+ @socket_thread = nil
69
+ @first_data_sent = false
70
+ @streams = {}
71
+ end
65
72
  end
66
73
 
67
74
  def new_stream
@@ -85,8 +92,7 @@ module NetHttp2
85
92
 
86
93
  return if @socket_thread
87
94
 
88
- main_thread = Thread.current
89
- @socket = new_socket
95
+ @socket = new_socket
90
96
 
91
97
  @socket_thread = Thread.new do
92
98
  begin
@@ -95,14 +101,14 @@ module NetHttp2
95
101
  rescue EOFError
96
102
  # socket closed
97
103
  init_vars
98
- main_thread.raise SocketError.new 'Socket was remotely closed'
104
+ raise SocketError.new 'Socket was remotely closed'
99
105
 
100
106
  rescue Exception => e
101
107
  # error on socket
102
108
  init_vars
103
- main_thread.raise e
109
+ raise e
104
110
  end
105
- end
111
+ end.tap { |t| t.abort_on_exception = true }
106
112
  end
107
113
  end
108
114
 
@@ -126,7 +132,11 @@ module NetHttp2
126
132
  end
127
133
 
128
134
  def new_socket
129
- NetHttp2::Socket.create(@uri, ssl: ssl?, ssl_context: @ssl_context, connect_timeout: @connect_timeout)
135
+ options = {
136
+ ssl: ssl?, ssl_context: @ssl_context, connect_timeout: @connect_timeout
137
+ }
138
+ PROXY_SETTINGS_KEYS.each { |k| options[k] = instance_variable_get("@#{k}") }
139
+ NetHttp2::Socket.create(@uri, options)
130
140
  end
131
141
 
132
142
  def ensure_sent_before_receiving
@@ -1,15 +1,18 @@
1
+ require 'cgi'
2
+
1
3
  module NetHttp2
2
4
 
3
5
  class Request
4
6
 
5
7
  DEFAULT_TIMEOUT = 60
6
8
 
7
- attr_reader :method, :uri, :path, :body, :timeout
9
+ attr_reader :method, :uri, :path, :params, :body, :timeout
8
10
 
9
11
  def initialize(method, uri, path, options={})
10
12
  @method = method
11
13
  @uri = uri
12
14
  @path = path
15
+ @params = options[:params] || {}
13
16
  @body = options[:body]
14
17
  @headers = options[:headers] || {}
15
18
  @timeout = options[:timeout] || DEFAULT_TIMEOUT
@@ -21,7 +24,7 @@ module NetHttp2
21
24
  @headers.merge!({
22
25
  ':scheme' => @uri.scheme,
23
26
  ':method' => @method.to_s.upcase,
24
- ':path' => @path,
27
+ ':path' => full_path,
25
28
  })
26
29
 
27
30
  @headers.merge!(':authority' => "#{@uri.host}:#{@uri.port}") unless @headers[':authority']
@@ -35,6 +38,12 @@ module NetHttp2
35
38
  @headers
36
39
  end
37
40
 
41
+ def full_path
42
+ path = @path
43
+ path += "?#{to_query(@params)}" unless @params.empty?
44
+ path
45
+ end
46
+
38
47
  def on(event, &block)
39
48
  raise ArgumentError, 'on event must provide a block' unless block_given?
40
49
 
@@ -46,5 +55,62 @@ module NetHttp2
46
55
  return unless @events[event]
47
56
  @events[event].each { |b| b.call(arg) }
48
57
  end
58
+
59
+ private
60
+
61
+ # The to_param and to_query code here below is a free adaptation from the original code in:
62
+ # <https://github.com/rails/rails/blob/v5.0.0.1/activesupport/lib/active_support/core_ext/object/to_query.rb>
63
+ # released under the following MIT license:
64
+ #
65
+ # Copyright (c) 2005-2016 David Heinemeier Hansson
66
+ #
67
+ # Permission is hereby granted, free of charge, to any person obtaining
68
+ # a copy of this software and associated documentation files (the
69
+ # "Software"), to deal in the Software without restriction, including
70
+ # without limitation the rights to use, copy, modify, merge, publish,
71
+ # distribute, sublicense, and/or sell copies of the Software, and to
72
+ # permit persons to whom the Software is furnished to do so, subject to
73
+ # the following conditions:
74
+ #
75
+ # The above copyright notice and this permission notice shall be
76
+ # included in all copies or substantial portions of the Software.
77
+ #
78
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
79
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
80
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
81
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
82
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
83
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
84
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
+
86
+ def to_param(element)
87
+ if element.is_a?(TrueClass) || element.is_a?(FalseClass) || element.is_a?(NilClass)
88
+ element
89
+ elsif element.is_a?(Array)
90
+ element.collect(&:to_param).join '/'
91
+ else
92
+ element.to_s.strip
93
+ end
94
+ end
95
+
96
+ def to_query(element, namespace_or_key = nil)
97
+ if element.is_a?(Hash)
98
+ element.collect do |key, value|
99
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
100
+ to_query(value, namespace_or_key ? "#{namespace_or_key}[#{key}]" : key)
101
+ end
102
+ end.compact.sort! * '&'
103
+ elsif element.is_a?(Array)
104
+ prefix = "#{namespace_or_key}[]"
105
+
106
+ if element.empty?
107
+ to_query(nil, prefix)
108
+ else
109
+ element.collect { |value| to_query(value, prefix) }.join '&'
110
+ end
111
+ else
112
+ "#{CGI.escape(to_param(namespace_or_key))}=#{CGI.escape(to_param(element).to_s)}"
113
+ end
114
+ end
49
115
  end
50
116
  end
@@ -3,15 +3,22 @@ module NetHttp2
3
3
  module Socket
4
4
 
5
5
  def self.create(uri, options)
6
- options[:ssl] ? ssl_socket(uri, options) : tcp_socket(uri, options)
6
+ return ssl_socket(uri, options) if options[:ssl]
7
+ return proxy_tcp_socket(uri, options) if options[:proxy_addr]
8
+
9
+ tcp_socket(uri, options)
7
10
  end
8
11
 
9
12
  def self.ssl_socket(uri, options)
10
- tcp = tcp_socket(uri, options)
13
+ tcp = if options[:proxy_addr]
14
+ proxy_tcp_socket(uri, options)
15
+ else
16
+ tcp_socket(uri, options)
17
+ end
11
18
 
12
19
  socket = OpenSSL::SSL::SSLSocket.new(tcp, options[:ssl_context])
13
20
  socket.sync_close = true
14
- socket.hostname = uri.hostname
21
+ socket.hostname = options[:proxy_addr] || uri.host
15
22
 
16
23
  socket.connect
17
24
 
@@ -46,5 +53,49 @@ module NetHttp2
46
53
 
47
54
  socket
48
55
  end
56
+
57
+ def self.proxy_tcp_socket(uri, options)
58
+ proxy_addr = options[:proxy_addr]
59
+ proxy_port = options[:proxy_port]
60
+ proxy_user = options[:proxy_user]
61
+ proxy_pass = options[:proxy_pass]
62
+
63
+ proxy_uri = URI.parse("#{proxy_addr}:#{proxy_port}")
64
+ proxy_socket = tcp_socket(proxy_uri, options)
65
+
66
+ # The majority of proxies do not explicitly support HTTP/2 protocol,
67
+ # while they successfully create a TCP tunnel
68
+ # which can pass through binary data of HTTP/2 connection.
69
+ # So we’ll keep HTTP/1.1
70
+ http_version = '1.1'
71
+
72
+ buf = "CONNECT #{uri.host}:#{uri.port} HTTP/#{http_version}\r\n"
73
+ buf << "Host: #{uri.host}:#{uri.port}\r\n"
74
+ if proxy_user
75
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
76
+ credential.delete!("\r\n")
77
+ buf << "Proxy-Authorization: Basic #{credential}\r\n"
78
+ end
79
+ buf << "\r\n"
80
+ proxy_socket.write(buf)
81
+ validate_proxy_response!(proxy_socket)
82
+
83
+ proxy_socket
84
+ end
85
+
86
+ private
87
+
88
+ def self.validate_proxy_response!(socket)
89
+ result = ''
90
+ loop do
91
+ line = socket.gets
92
+ break if !line || line.strip.empty?
93
+
94
+ result << line
95
+ end
96
+ return if result =~ /HTTP\/\d(?:\.\d)?\s+2\d\d\s/
97
+
98
+ raise(StandardError, "Proxy connection failure:\n#{result}")
99
+ end
49
100
  end
50
101
  end
@@ -1,3 +1,3 @@
1
1
  module NetHttp2
2
- VERSION = "0.14.0"
2
+ VERSION = "0.14.1"
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.14.0
4
+ version: 0.14.1
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-09-30 00:00:00.000000000 Z
11
+ date: 2016-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2