httpx 1.0.2 → 1.1.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 +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
|