httpx 1.0.2 → 1.1.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 +4 -4
- data/README.md +2 -2
- data/doc/release_notes/1_1_0.md +32 -0
- data/lib/httpx/adapters/faraday.rb +28 -19
- data/lib/httpx/connection/http1.rb +10 -3
- data/lib/httpx/connection/http2.rb +1 -1
- data/lib/httpx/connection.rb +51 -12
- data/lib/httpx/domain_name.rb +6 -2
- data/lib/httpx/errors.rb +32 -0
- data/lib/httpx/io/ssl.rb +3 -1
- data/lib/httpx/io/tcp.rb +4 -2
- data/lib/httpx/io.rb +5 -1
- data/lib/httpx/options.rb +48 -1
- data/lib/httpx/plugins/expect.rb +10 -8
- data/lib/httpx/pool.rb +0 -1
- data/lib/httpx/request/body.rb +22 -9
- data/lib/httpx/request.rb +63 -4
- data/lib/httpx/resolver/native.rb +2 -2
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/response/body.rb +30 -5
- data/lib/httpx/response/buffer.rb +20 -14
- data/lib/httpx/response.rb +95 -16
- data/lib/httpx/selector.rb +2 -2
- data/lib/httpx/session.rb +61 -1
- data/lib/httpx/timers.rb +33 -8
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +4 -1
- data/sig/io/tcp.rbs +1 -1
- data/sig/options.rbs +2 -2
- data/sig/pool.rbs +1 -1
- data/sig/request/body.rbs +0 -2
- data/sig/request.rbs +9 -3
- data/sig/resolver/native.rbs +1 -1
- data/sig/resolver.rbs +1 -1
- data/sig/response/body.rbs +0 -1
- data/sig/response.rbs +11 -3
- data/sig/timers.rbs +17 -7
- data/sig/transcoder/utils/inflater.rbs +12 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ef9f0a027f7a313547ca11bce6cf2c5a3eb72075b2cee0be13448d4aa7ec2b6
|
4
|
+
data.tar.gz: 0021a6c5e4968dd68cb23a157c5ce0dd84021bf134eb6c8227c5b145772e5812
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14687b4930aff7fef833058a27d5b26e4a9c327fcbf779f88d3b2b7766a861a26845285054ccde501a5a8296fdfc3c87a5f7e56eafc58fabeecd383512d36e8c
|
7
|
+
data.tar.gz: 95ee1b01958d2c680e194baa2f3136464c775038e9cd87d7d712d88998f48bbd385d6f3cc1173d2bda023102c4ca4661221aabd03d0d8a0a62e3006a79694851
|
data/README.md
CHANGED
@@ -108,8 +108,8 @@ HTTPX.get(
|
|
108
108
|
```ruby
|
109
109
|
response = HTTPX.get("https://www.google.com", params: { q: "me" })
|
110
110
|
response = HTTPX.post("https://www.nghttp2.org/httpbin/post", form: {name: "John", age: "22"})
|
111
|
-
response = HTTPX.plugin(:
|
112
|
-
.
|
111
|
+
response = HTTPX.plugin(:basic_auth)
|
112
|
+
.basic_auth("user", "pass")
|
113
113
|
.get("https://www.google.com")
|
114
114
|
|
115
115
|
# more complex client objects can be cached, and are thread-safe
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# 1.1.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
A function, `#peer_address`, was added to the response object, which returns the IP (either a string or an `IPAddr` object) from the socket used to get the response from.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
response = HTTPX.get("https://example.com")
|
9
|
+
response.peer_address #=> #<IPAddr: IPv4:93.184.216.34/255.255.255.255>
|
10
|
+
```
|
11
|
+
|
12
|
+
error responses will also expose an IP address via `#peer_address` as long a connection happened before the error.
|
13
|
+
|
14
|
+
## Improvements
|
15
|
+
|
16
|
+
* A performance regression involving the new default timeouts has been fixed, which could cause significant overhead in "multiple requests in sequence" scenarios, and was clearly visible in benchmarks.
|
17
|
+
* this regression will still be seen in jruby due to a bug, which fix will be released in jruby 9.4.5.0.
|
18
|
+
* HTTP/1.1 connections are now set to handle as many requests as they can by default (instead of the past default of max 200, at which point they'd be recycled).
|
19
|
+
* tolerate the inexistence of `openssl` in the installed ruby, like `net-http` does.
|
20
|
+
* `on_connection_opened` and `on_connection_closed` will yield the `OpenSSL::SSL::SSLSocket` instance for `https` backed origins (instead of always the `Socket` instance).
|
21
|
+
|
22
|
+
## Bugfixes
|
23
|
+
|
24
|
+
* when using the `:native` resolver (default option), a default of 1 for ndots is set, for systems which do not set one.
|
25
|
+
* replaced usage of `Float::INFINITY` with `nil` for timeout defaults, as the former can't be used in IO wait functions.
|
26
|
+
* `faraday` adapter timeout setup now maps to `:read_timeout` and `:write_timeout` options from `httpx`.
|
27
|
+
* fixed HTTP/1.1 connection recycling on number of max requests exhausted.
|
28
|
+
* `response.json` will now work when "content-type" header is set to "application/hal+json".
|
29
|
+
|
30
|
+
## Chore
|
31
|
+
|
32
|
+
* when using the `:cookies` plugin, a warning message to install the idnx message will only be emitted if the cookie domain is an IDN (this message was being shown all the time since v1 release).
|
@@ -69,38 +69,47 @@ module Faraday
|
|
69
69
|
timeout_options = {}
|
70
70
|
req_opts = env.request
|
71
71
|
if (sec = request_timeout(:read, req_opts))
|
72
|
-
timeout_options[:
|
72
|
+
timeout_options[:read_timeout] = sec
|
73
73
|
end
|
74
74
|
|
75
75
|
if (sec = request_timeout(:write, req_opts))
|
76
|
-
timeout_options[:
|
76
|
+
timeout_options[:write_timeout] = sec
|
77
77
|
end
|
78
78
|
|
79
79
|
if (sec = request_timeout(:open, req_opts))
|
80
80
|
timeout_options[:connect_timeout] = sec
|
81
81
|
end
|
82
82
|
|
83
|
-
ssl_options = {}
|
84
|
-
|
85
|
-
unless env.ssl.verify.nil?
|
86
|
-
ssl_options[:verify_mode] = env.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
87
|
-
end
|
88
|
-
|
89
|
-
ssl_options[:ca_file] = env.ssl.ca_file if env.ssl.ca_file
|
90
|
-
ssl_options[:ca_path] = env.ssl.ca_path if env.ssl.ca_path
|
91
|
-
ssl_options[:cert_store] = env.ssl.cert_store if env.ssl.cert_store
|
92
|
-
ssl_options[:cert] = env.ssl.client_cert if env.ssl.client_cert
|
93
|
-
ssl_options[:key] = env.ssl.client_key if env.ssl.client_key
|
94
|
-
ssl_options[:ssl_version] = env.ssl.version if env.ssl.version
|
95
|
-
ssl_options[:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
|
96
|
-
ssl_options[:min_version] = env.ssl.min_version if env.ssl.min_version
|
97
|
-
ssl_options[:max_version] = env.ssl.max_version if env.ssl.max_version
|
98
|
-
|
99
83
|
{
|
100
|
-
ssl:
|
84
|
+
ssl: ssl_options_from_env(env),
|
101
85
|
timeout: timeout_options,
|
102
86
|
}
|
103
87
|
end
|
88
|
+
|
89
|
+
if defined?(::OpenSSL)
|
90
|
+
def ssl_options_from_env(env)
|
91
|
+
ssl_options = {}
|
92
|
+
|
93
|
+
unless env.ssl.verify.nil?
|
94
|
+
ssl_options[:verify_mode] = env.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
95
|
+
end
|
96
|
+
|
97
|
+
ssl_options[:ca_file] = env.ssl.ca_file if env.ssl.ca_file
|
98
|
+
ssl_options[:ca_path] = env.ssl.ca_path if env.ssl.ca_path
|
99
|
+
ssl_options[:cert_store] = env.ssl.cert_store if env.ssl.cert_store
|
100
|
+
ssl_options[:cert] = env.ssl.client_cert if env.ssl.client_cert
|
101
|
+
ssl_options[:key] = env.ssl.client_key if env.ssl.client_key
|
102
|
+
ssl_options[:ssl_version] = env.ssl.version if env.ssl.version
|
103
|
+
ssl_options[:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
|
104
|
+
ssl_options[:min_version] = env.ssl.min_version if env.ssl.min_version
|
105
|
+
ssl_options[:max_version] = env.ssl.max_version if env.ssl.max_version
|
106
|
+
ssl_options
|
107
|
+
end
|
108
|
+
else
|
109
|
+
def ssl_options_from_env(*)
|
110
|
+
{}
|
111
|
+
end
|
112
|
+
end
|
104
113
|
end
|
105
114
|
|
106
115
|
include RequestMixin
|
@@ -15,7 +15,7 @@ module HTTPX
|
|
15
15
|
def initialize(buffer, options)
|
16
16
|
@options = Options.new(options)
|
17
17
|
@max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
|
18
|
-
@max_requests = @options.max_requests
|
18
|
+
@max_requests = @options.max_requests
|
19
19
|
@parser = Parser::HTTP1.new(self)
|
20
20
|
@buffer = buffer
|
21
21
|
@version = [1, 1]
|
@@ -184,7 +184,14 @@ module HTTPX
|
|
184
184
|
manage_connection(response)
|
185
185
|
end
|
186
186
|
|
187
|
-
|
187
|
+
if exhausted?
|
188
|
+
@pending.concat(@requests)
|
189
|
+
@requests.clear
|
190
|
+
|
191
|
+
emit(:exhausted)
|
192
|
+
else
|
193
|
+
send(@pending.shift) unless @pending.empty?
|
194
|
+
end
|
188
195
|
end
|
189
196
|
|
190
197
|
def handle_error(ex)
|
@@ -287,7 +294,7 @@ module HTTPX
|
|
287
294
|
|
288
295
|
connection = request.headers["connection"]
|
289
296
|
|
290
|
-
connection ||= if request.
|
297
|
+
connection ||= if request.persistent?
|
291
298
|
# when in a persistent connection, the request can't be at
|
292
299
|
# the edge of a renegotiation
|
293
300
|
if @requests.index(request) + 1 < @max_requests
|
@@ -35,7 +35,7 @@ module HTTPX
|
|
35
35
|
@handshake_completed = false
|
36
36
|
@wait_for_handshake = @settings.key?(:wait_for_handshake) ? @settings.delete(:wait_for_handshake) : true
|
37
37
|
@max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
|
38
|
-
@max_requests = @options.max_requests
|
38
|
+
@max_requests = @options.max_requests
|
39
39
|
init_connection
|
40
40
|
end
|
41
41
|
|
data/lib/httpx/connection.rb
CHANGED
@@ -70,6 +70,8 @@ module HTTPX
|
|
70
70
|
@inflight = 0
|
71
71
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
72
72
|
|
73
|
+
@intervals = []
|
74
|
+
|
73
75
|
self.addresses = @options.addresses if @options.addresses
|
74
76
|
end
|
75
77
|
|
@@ -213,6 +215,9 @@ module HTTPX
|
|
213
215
|
|
214
216
|
def call
|
215
217
|
case @state
|
218
|
+
when :idle
|
219
|
+
connect
|
220
|
+
consume
|
216
221
|
when :closed
|
217
222
|
return
|
218
223
|
when :closing
|
@@ -294,7 +299,15 @@ module HTTPX
|
|
294
299
|
@state == :open || @state == :inactive
|
295
300
|
end
|
296
301
|
|
297
|
-
def
|
302
|
+
def handle_socket_timeout(interval)
|
303
|
+
@intervals.delete_if(&:elapsed?)
|
304
|
+
|
305
|
+
unless @intervals.empty?
|
306
|
+
# remove the intervals which will elapse
|
307
|
+
|
308
|
+
return
|
309
|
+
end
|
310
|
+
|
298
311
|
error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
299
312
|
error.set_backtrace(caller)
|
300
313
|
on_error(error)
|
@@ -449,6 +462,7 @@ module HTTPX
|
|
449
462
|
|
450
463
|
def send_request_to_parser(request)
|
451
464
|
@inflight += 1
|
465
|
+
request.peer_address = @io.ip
|
452
466
|
parser.send(request)
|
453
467
|
|
454
468
|
set_request_timeouts(request)
|
@@ -655,19 +669,26 @@ module HTTPX
|
|
655
669
|
|
656
670
|
def set_request_timeouts(request)
|
657
671
|
write_timeout = request.write_timeout
|
658
|
-
request.once(:headers) do
|
659
|
-
@timers.after(write_timeout) { write_timeout_callback(request, write_timeout) }
|
660
|
-
end unless write_timeout.nil? || write_timeout.infinite?
|
661
|
-
|
662
672
|
read_timeout = request.read_timeout
|
663
|
-
request.once(:done) do
|
664
|
-
@timers.after(read_timeout) { read_timeout_callback(request, read_timeout) }
|
665
|
-
end unless read_timeout.nil? || read_timeout.infinite?
|
666
|
-
|
667
673
|
request_timeout = request.request_timeout
|
668
|
-
|
669
|
-
|
670
|
-
|
674
|
+
|
675
|
+
unless write_timeout.nil? || write_timeout.infinite?
|
676
|
+
set_request_timeout(request, write_timeout, :headers, %i[done response]) do
|
677
|
+
write_timeout_callback(request, write_timeout)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
unless read_timeout.nil? || read_timeout.infinite?
|
682
|
+
set_request_timeout(request, read_timeout, :done, :response) do
|
683
|
+
read_timeout_callback(request, read_timeout)
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
return if request_timeout.nil? || request_timeout.infinite?
|
688
|
+
|
689
|
+
set_request_timeout(request, request_timeout, :headers, :response) do
|
690
|
+
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
691
|
+
end
|
671
692
|
end
|
672
693
|
|
673
694
|
def write_timeout_callback(request, write_timeout)
|
@@ -688,6 +709,24 @@ module HTTPX
|
|
688
709
|
on_error(error)
|
689
710
|
end
|
690
711
|
|
712
|
+
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
713
|
+
request.once(start_event) do
|
714
|
+
interval = @timers.after(timeout, callback)
|
715
|
+
|
716
|
+
Array(finish_events).each do |event|
|
717
|
+
# clean up reques timeouts if the connection errors out
|
718
|
+
request.once(event) do
|
719
|
+
if @intervals.include?(interval)
|
720
|
+
interval.delete(callback)
|
721
|
+
@intervals.delete(interval) if interval.no_callbacks?
|
722
|
+
end
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
@intervals << interval
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
691
730
|
class << self
|
692
731
|
def parser_type(protocol)
|
693
732
|
case protocol
|
data/lib/httpx/domain_name.rb
CHANGED
@@ -61,8 +61,12 @@ module HTTPX
|
|
61
61
|
# Normalizes a _domain_ using the Punycode algorithm as necessary.
|
62
62
|
# The result will be a downcased, ASCII-only string.
|
63
63
|
def normalize(domain)
|
64
|
-
|
65
|
-
|
64
|
+
unless domain.ascii_only?
|
65
|
+
domain = domain.chomp(".").unicode_normalize(:nfc)
|
66
|
+
domain = Punycode.encode_hostname(domain)
|
67
|
+
end
|
68
|
+
|
69
|
+
domain.downcase
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
data/lib/httpx/errors.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
+
# the default exception class for exceptions raised by HTTPX.
|
4
5
|
class Error < StandardError; end
|
5
6
|
|
6
7
|
class UnsupportedSchemeError < Error; end
|
7
8
|
|
8
9
|
class ConnectionError < Error; end
|
9
10
|
|
11
|
+
# Error raised when there was a timeout. Its subclasses allow for finer-grained
|
12
|
+
# control of which timeout happened.
|
10
13
|
class TimeoutError < Error
|
14
|
+
# The timeout value which caused this error to be raised.
|
11
15
|
attr_reader :timeout
|
12
16
|
|
17
|
+
# initializes the timeout exception with the +timeout+ causing the error, and the
|
18
|
+
# error +message+ for it.
|
13
19
|
def initialize(timeout, message)
|
14
20
|
@timeout = timeout
|
15
21
|
super(message)
|
16
22
|
end
|
17
23
|
|
24
|
+
# clones this error into a HTTPX::ConnectionTimeoutError.
|
18
25
|
def to_connection_error
|
19
26
|
ex = ConnectTimeoutError.new(@timeout, message)
|
20
27
|
ex.set_backtrace(backtrace)
|
@@ -22,11 +29,19 @@ module HTTPX
|
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
32
|
+
# Error raised when there was a timeout establishing the connection to a server.
|
33
|
+
# This may be raised due to timeouts during TCP and TLS (when applicable) connection
|
34
|
+
# establishment.
|
25
35
|
class ConnectTimeoutError < TimeoutError; end
|
26
36
|
|
37
|
+
# Error raised when there was a timeout while sending a request, or receiving a response
|
38
|
+
# from the server.
|
27
39
|
class RequestTimeoutError < TimeoutError
|
40
|
+
# The HTTPX::Request request object this exception refers to.
|
28
41
|
attr_reader :request
|
29
42
|
|
43
|
+
# initializes the exception with the +request+ and +response+ it refers to, and the
|
44
|
+
# +timeout+ causing the error, and the
|
30
45
|
def initialize(request, response, timeout)
|
31
46
|
@request = request
|
32
47
|
@response = response
|
@@ -38,19 +53,28 @@ module HTTPX
|
|
38
53
|
end
|
39
54
|
end
|
40
55
|
|
56
|
+
# Error raised when there was a timeout while receiving a response from the server.
|
41
57
|
class ReadTimeoutError < RequestTimeoutError; end
|
42
58
|
|
59
|
+
# Error raised when there was a timeout while sending a request from the server.
|
43
60
|
class WriteTimeoutError < RequestTimeoutError; end
|
44
61
|
|
62
|
+
# Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
|
45
63
|
class SettingsTimeoutError < TimeoutError; end
|
46
64
|
|
65
|
+
# Error raised when there was a timeout while resolving a domain to an IP.
|
47
66
|
class ResolveTimeoutError < TimeoutError; end
|
48
67
|
|
68
|
+
# Error raised when there was an error while resolving a domain to an IP.
|
49
69
|
class ResolveError < Error; end
|
50
70
|
|
71
|
+
# Error raised when there was an error while resolving a domain to an IP
|
72
|
+
# using a HTTPX::Resolver::Native resolver.
|
51
73
|
class NativeResolveError < ResolveError
|
52
74
|
attr_reader :connection, :host
|
53
75
|
|
76
|
+
# initializes the exception with the +connection+ it refers to, the +host+ domain
|
77
|
+
# which failed to resolve, and the error +message+.
|
54
78
|
def initialize(connection, host, message = "Can't resolve #{host}")
|
55
79
|
@connection = connection
|
56
80
|
@host = host
|
@@ -58,18 +82,26 @@ module HTTPX
|
|
58
82
|
end
|
59
83
|
end
|
60
84
|
|
85
|
+
# The exception class for HTTP responses with 4xx or 5xx status.
|
61
86
|
class HTTPError < Error
|
87
|
+
# The HTTPX::Response response object this exception refers to.
|
62
88
|
attr_reader :response
|
63
89
|
|
90
|
+
# Creates the instance and assigns the HTTPX::Response +response+.
|
64
91
|
def initialize(response)
|
65
92
|
@response = response
|
66
93
|
super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
|
67
94
|
end
|
68
95
|
|
96
|
+
# The HTTP response status.
|
97
|
+
#
|
98
|
+
# error.status #=> 404
|
69
99
|
def status
|
70
100
|
@response.status
|
71
101
|
end
|
72
102
|
end
|
73
103
|
|
104
|
+
# error raised when a request was sent a server which can't reproduce a response, and
|
105
|
+
# has therefore returned an HTTP response using the 421 status code.
|
74
106
|
class MisdirectedRequestError < HTTPError; end
|
75
107
|
end
|
data/lib/httpx/io/ssl.rb
CHANGED
@@ -114,7 +114,9 @@ module HTTPX
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def try_ssl_connect
|
117
|
-
|
117
|
+
ret = @io.connect_nonblock(exception: false)
|
118
|
+
log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
|
119
|
+
case ret
|
118
120
|
when :wait_readable
|
119
121
|
@interests = :r
|
120
122
|
return
|
data/lib/httpx/io/tcp.rb
CHANGED
@@ -41,7 +41,7 @@ module HTTPX
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def socket
|
44
|
-
@io
|
44
|
+
@io
|
45
45
|
end
|
46
46
|
|
47
47
|
def add_addresses(addrs)
|
@@ -95,7 +95,9 @@ module HTTPX
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def try_connect
|
98
|
-
|
98
|
+
ret = @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false)
|
99
|
+
log(level: 3, color: :cyan) { "TCP CONNECT: #{ret}..." }
|
100
|
+
case ret
|
99
101
|
when :wait_readable
|
100
102
|
@interests = :r
|
101
103
|
return
|
data/lib/httpx/io.rb
CHANGED
data/lib/httpx/options.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require "socket"
|
4
4
|
|
5
5
|
module HTTPX
|
6
|
+
# Contains a set of options which are passed and shared across from session to its requests or
|
7
|
+
# responses.
|
6
8
|
class Options
|
7
9
|
BUFFER_SIZE = 1 << 14
|
8
10
|
WINDOW_SIZE = 1 << 14 # 16K
|
@@ -10,7 +12,7 @@ module HTTPX
|
|
10
12
|
KEEP_ALIVE_TIMEOUT = 20
|
11
13
|
SETTINGS_TIMEOUT = 10
|
12
14
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
13
|
-
REQUEST_TIMEOUT = OPERATION_TIMEOUT =
|
15
|
+
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
14
16
|
|
15
17
|
# https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
|
16
18
|
ip_address_families = begin
|
@@ -25,6 +27,7 @@ module HTTPX
|
|
25
27
|
end
|
26
28
|
|
27
29
|
DEFAULT_OPTIONS = {
|
30
|
+
:max_requests => Float::INFINITY,
|
28
31
|
:debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
|
29
32
|
:debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
|
30
33
|
:ssl => {},
|
@@ -81,6 +84,50 @@ module HTTPX
|
|
81
84
|
end
|
82
85
|
end
|
83
86
|
|
87
|
+
# creates a new options instance from a given hash, which optionally define the following:
|
88
|
+
#
|
89
|
+
# :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
|
90
|
+
# :debug_level :: the log level of messages (can be 1, 2, or 3).
|
91
|
+
# :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
|
92
|
+
# :http2_settings :: a hash of options to be passed to a HTTP2Next::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
|
93
|
+
# :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
|
94
|
+
# like ALPN (defaults to <tt>"http/1.1"</tt>)
|
95
|
+
# :supported_compression_formats :: list of compressions supported by the transcoder layer (defaults to <tt>%w[gzip deflate]</tt>).
|
96
|
+
# :decompress_response_body :: whether to auto-decompress response body (defaults to <tt>true</tt>).
|
97
|
+
# :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
|
98
|
+
# :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
|
99
|
+
# <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>
|
100
|
+
# and <tt>:request_timeout</tt>
|
101
|
+
# :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
|
102
|
+
# :window_size :: number of bytes to read from a socket
|
103
|
+
# :buffer_size :: internal read and write buffer size in bytes
|
104
|
+
# :body_threshold_size :: maximum size in bytes of response payload that is buffered in memory.
|
105
|
+
# :request_class :: class used to instantiate a request
|
106
|
+
# :response_class :: class used to instantiate a response
|
107
|
+
# :headers_class :: class used to instantiate headers
|
108
|
+
# :request_body_class :: class used to instantiate a request body
|
109
|
+
# :response_body_class :: class used to instantiate a response body
|
110
|
+
# :connection_class :: class used to instantiate connections
|
111
|
+
# :options_class :: class used to instantiate options
|
112
|
+
# :transport :: type of transport to use (set to "unix" for UNIX sockets)
|
113
|
+
# :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
|
114
|
+
# paths should be used for UNIX sockets instead)
|
115
|
+
# :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
|
116
|
+
# :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
|
117
|
+
# :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
|
118
|
+
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
|
119
|
+
# :resolver_options :: hash of options passed to the resolver
|
120
|
+
# :ip_families :: which socket families are supported (system-dependent)
|
121
|
+
# :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
|
122
|
+
# :base_path :: path to prefix given relative paths with (ex: "/v2")
|
123
|
+
# :max_concurrent_requests :: max number of requests which can be set concurrently
|
124
|
+
# :max_requests :: max number of requests which can be made on socket before it reconnects.
|
125
|
+
# :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
|
126
|
+
# :form :: hash of array of key-values which will be form-or-multipart-encoded in requests body payload.
|
127
|
+
# :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
|
128
|
+
# :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
|
129
|
+
#
|
130
|
+
# This list of options are enhanced with each loaded plugin, see the plugin docs for details.
|
84
131
|
def initialize(options = {})
|
85
132
|
do_initialize(options)
|
86
133
|
freeze
|
data/lib/httpx/plugins/expect.rb
CHANGED
@@ -75,14 +75,16 @@ module HTTPX
|
|
75
75
|
|
76
76
|
return unless request.headers["expect"] == "100-continue"
|
77
77
|
|
78
|
-
request.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
expect_timeout = request.options.expect_timeout
|
79
|
+
|
80
|
+
return if expect_timeout.nil? || expect_timeout.infinite?
|
81
|
+
|
82
|
+
set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
|
83
|
+
# expect timeout expired
|
84
|
+
if request.state == :expect && !request.expects?
|
85
|
+
Expect.no_expect_store << request.origin
|
86
|
+
request.headers.delete("expect")
|
87
|
+
consume
|
86
88
|
end
|
87
89
|
end
|
88
90
|
end
|
data/lib/httpx/pool.rb
CHANGED
data/lib/httpx/request/body.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
+
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
|
4
5
|
class Request::Body < SimpleDelegator
|
5
6
|
class << self
|
6
7
|
def new(_, options)
|
@@ -10,13 +11,11 @@ module HTTPX
|
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
# inits the instance with the request +headers+ and +options+, which contain the payload definition.
|
15
15
|
def initialize(headers, options)
|
16
16
|
@headers = headers
|
17
|
-
@threshold_size = options.body_threshold_size
|
18
17
|
|
19
|
-
# forego compression in the Range
|
18
|
+
# forego compression in the Range request case
|
20
19
|
if @headers.key?("range")
|
21
20
|
@headers.delete("accept-encoding")
|
22
21
|
else
|
@@ -32,6 +31,7 @@ module HTTPX
|
|
32
31
|
super(@body)
|
33
32
|
end
|
34
33
|
|
34
|
+
# consumes and yields the request payload in chunks.
|
35
35
|
def each(&block)
|
36
36
|
return enum_for(__method__) unless block
|
37
37
|
return if @body.nil?
|
@@ -46,12 +46,14 @@ module HTTPX
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
# if the +@body+ is rewindable, it rewinnds it.
|
49
50
|
def rewind
|
50
51
|
return if empty?
|
51
52
|
|
52
53
|
@body.rewind if @body.respond_to?(:rewind)
|
53
54
|
end
|
54
55
|
|
56
|
+
# return +true+ if the +body+ has been fully drained (or does nnot exist).
|
55
57
|
def empty?
|
56
58
|
return true if @body.nil?
|
57
59
|
return false if chunked?
|
@@ -59,28 +61,33 @@ module HTTPX
|
|
59
61
|
@body.bytesize.zero?
|
60
62
|
end
|
61
63
|
|
64
|
+
# returns the +@body+ payload size in bytes.
|
62
65
|
def bytesize
|
63
66
|
return 0 if @body.nil?
|
64
67
|
|
65
68
|
@body.bytesize
|
66
69
|
end
|
67
70
|
|
71
|
+
# sets the body to yield using chunked trannsfer encoding format.
|
68
72
|
def stream(body)
|
69
73
|
encoded = body
|
70
74
|
encoded = Transcoder::Chunker.encode(body.enum_for(:each)) if chunked?
|
71
75
|
encoded
|
72
76
|
end
|
73
77
|
|
78
|
+
# returns whether the body yields infinitely.
|
74
79
|
def unbounded_body?
|
75
80
|
return @unbounded_body if defined?(@unbounded_body)
|
76
81
|
|
77
82
|
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
78
83
|
end
|
79
84
|
|
85
|
+
# returns whether the chunked transfer encoding header is set.
|
80
86
|
def chunked?
|
81
87
|
@headers["transfer-encoding"] == "chunked"
|
82
88
|
end
|
83
89
|
|
90
|
+
# sets the chunked transfer encoding header.
|
84
91
|
def chunk!
|
85
92
|
@headers.add("transfer-encoding", "chunked")
|
86
93
|
end
|
@@ -94,6 +101,13 @@ module HTTPX
|
|
94
101
|
|
95
102
|
private
|
96
103
|
|
104
|
+
# wraps the given body with the appropriate encoder.
|
105
|
+
#
|
106
|
+
# ..., json: { foo: "bar" }) #=> json encoder
|
107
|
+
# ..., form: { foo: "bar" }) #=> form urlencoded encoder
|
108
|
+
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
109
|
+
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
110
|
+
# ..., form: { body: "bla") }) #=> raw data encoder
|
97
111
|
def initialize_body(options)
|
98
112
|
@body = if options.body
|
99
113
|
Transcoder::Body.encode(options.body)
|
@@ -105,11 +119,7 @@ module HTTPX
|
|
105
119
|
Transcoder::Xml.encode(options.xml)
|
106
120
|
end
|
107
121
|
|
108
|
-
return unless @body
|
109
|
-
|
110
|
-
return unless options.compress_request_body
|
111
|
-
|
112
|
-
return unless @headers.key?("content-encoding")
|
122
|
+
return unless @body && options.compress_request_body && @headers.key?("content-encoding")
|
113
123
|
|
114
124
|
@headers.get("content-encoding").each do |encoding|
|
115
125
|
@body = self.class.initialize_deflater_body(@body, encoding)
|
@@ -117,6 +127,7 @@ module HTTPX
|
|
117
127
|
end
|
118
128
|
|
119
129
|
class << self
|
130
|
+
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
120
131
|
def initialize_deflater_body(body, encoding)
|
121
132
|
case encoding
|
122
133
|
when "gzip"
|
@@ -132,11 +143,13 @@ module HTTPX
|
|
132
143
|
end
|
133
144
|
end
|
134
145
|
|
146
|
+
# Wrapper yielder which can be used with functions which expect an IO writer.
|
135
147
|
class ProcIO
|
136
148
|
def initialize(block)
|
137
149
|
@block = block
|
138
150
|
end
|
139
151
|
|
152
|
+
# Implementation the IO write protocol, which yield the given chunk to +@block+.
|
140
153
|
def write(data)
|
141
154
|
@block.call(data.dup)
|
142
155
|
data.bytesize
|