net-http2 0.14.0 → 0.14.1

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: 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