httpx 0.21.0 → 1.2.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 +4 -4
- data/LICENSE.txt +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +4 -4
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_20_0.md +1 -1
- data/doc/release_notes/0_21_0.md +7 -5
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/lib/httpx/adapters/datadog.rb +100 -106
- data/lib/httpx/adapters/faraday.rb +143 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +33 -17
- data/lib/httpx/altsvc.rb +61 -24
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +62 -37
- data/lib/httpx/connection/http2.rb +16 -27
- data/lib/httpx/connection.rb +213 -120
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +34 -2
- data/lib/httpx/extensions.rb +4 -134
- data/lib/httpx/io/ssl.rb +77 -71
- data/lib/httpx/io/tcp.rb +46 -70
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +6 -13
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +168 -110
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
- data/lib/httpx/plugins/aws_sigv4.rb +5 -6
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
- data/lib/httpx/plugins/circuit_breaker.rb +30 -7
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +20 -10
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
- data/lib/httpx/plugins/expect.rb +15 -13
- data/lib/httpx/plugins/follow_redirects.rb +71 -29
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +35 -29
- data/lib/httpx/plugins/h2c.rb +25 -18
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +170 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -10
- data/lib/httpx/plugins/proxy/socks4.rb +8 -6
- data/lib/httpx/plugins/proxy/socks5.rb +10 -8
- data/lib/httpx/plugins/proxy.rb +69 -67
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +3 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +34 -17
- data/lib/httpx/plugins/response_cache.rb +6 -6
- data/lib/httpx/plugins/retries.rb +61 -12
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +27 -32
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +10 -8
- data/lib/httpx/pool.rb +85 -23
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +86 -121
- data/lib/httpx/resolver/https.rb +54 -17
- data/lib/httpx/resolver/multi.rb +8 -12
- data/lib/httpx/resolver/native.rb +163 -70
- data/lib/httpx/resolver/resolver.rb +28 -13
- data/lib/httpx/resolver/system.rb +15 -10
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +113 -211
- data/lib/httpx/selector.rb +2 -4
- data/lib/httpx/session.rb +91 -64
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +28 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +2 -5
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +0 -5
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +1 -0
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +10 -9
- data/sig/connection/http1.rbs +5 -4
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +46 -24
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +26 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +10 -0
- data/sig/options.rbs +28 -12
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +13 -3
- data/sig/plugins/compression.rbs +6 -4
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +11 -2
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +2 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/socks4.rbs +4 -4
- data/sig/plugins/proxy/socks5.rbs +2 -2
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +10 -4
- data/sig/plugins/response_cache.rbs +12 -3
- data/sig/plugins/retries.rbs +28 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +5 -4
- data/sig/request/body.rbs +40 -0
- data/sig/request.rbs +12 -28
- data/sig/resolver/https.rbs +7 -2
- data/sig/resolver/native.rbs +10 -4
- data/sig/resolver/resolver.rbs +6 -4
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +17 -38
- data/sig/session.rbs +24 -18
- data/sig/timers.rbs +17 -7
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +4 -2
- data/sig/{plugins → transcoder}/multipart.rbs +3 -12
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +1 -1
- data/sig/transcoder.rbs +22 -7
- data/sig/utils.rbs +2 -0
- metadata +127 -40
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -13
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
|
|
5
|
+
class Request::Body < SimpleDelegator
|
|
6
|
+
class << self
|
|
7
|
+
def new(_, options)
|
|
8
|
+
return options.body if options.body.is_a?(self)
|
|
9
|
+
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# inits the instance with the request +headers+ and +options+, which contain the payload definition.
|
|
15
|
+
def initialize(headers, options)
|
|
16
|
+
@headers = headers
|
|
17
|
+
|
|
18
|
+
# forego compression in the Range request case
|
|
19
|
+
if @headers.key?("range")
|
|
20
|
+
@headers.delete("accept-encoding")
|
|
21
|
+
else
|
|
22
|
+
@headers["accept-encoding"] ||= options.supported_compression_formats
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
initialize_body(options)
|
|
26
|
+
|
|
27
|
+
return if @body.nil?
|
|
28
|
+
|
|
29
|
+
@headers["content-type"] ||= @body.content_type
|
|
30
|
+
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
|
31
|
+
super(@body)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# consumes and yields the request payload in chunks.
|
|
35
|
+
def each(&block)
|
|
36
|
+
return enum_for(__method__) unless block
|
|
37
|
+
return if @body.nil?
|
|
38
|
+
|
|
39
|
+
body = stream(@body)
|
|
40
|
+
if body.respond_to?(:read)
|
|
41
|
+
::IO.copy_stream(body, ProcIO.new(block))
|
|
42
|
+
elsif body.respond_to?(:each)
|
|
43
|
+
body.each(&block)
|
|
44
|
+
else
|
|
45
|
+
block[body.to_s]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# if the +@body+ is rewindable, it rewinnds it.
|
|
50
|
+
def rewind
|
|
51
|
+
return if empty?
|
|
52
|
+
|
|
53
|
+
@body.rewind if @body.respond_to?(:rewind)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# return +true+ if the +body+ has been fully drained (or does nnot exist).
|
|
57
|
+
def empty?
|
|
58
|
+
return true if @body.nil?
|
|
59
|
+
return false if chunked?
|
|
60
|
+
|
|
61
|
+
@body.bytesize.zero?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# returns the +@body+ payload size in bytes.
|
|
65
|
+
def bytesize
|
|
66
|
+
return 0 if @body.nil?
|
|
67
|
+
|
|
68
|
+
@body.bytesize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# sets the body to yield using chunked trannsfer encoding format.
|
|
72
|
+
def stream(body)
|
|
73
|
+
return body unless chunked?
|
|
74
|
+
|
|
75
|
+
Transcoder::Chunker.encode(body.enum_for(:each))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# returns whether the body yields infinitely.
|
|
79
|
+
def unbounded_body?
|
|
80
|
+
return @unbounded_body if defined?(@unbounded_body)
|
|
81
|
+
|
|
82
|
+
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# returns whether the chunked transfer encoding header is set.
|
|
86
|
+
def chunked?
|
|
87
|
+
@headers["transfer-encoding"] == "chunked"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# sets the chunked transfer encoding header.
|
|
91
|
+
def chunk!
|
|
92
|
+
@headers.add("transfer-encoding", "chunked")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# :nocov:
|
|
96
|
+
def inspect
|
|
97
|
+
"#<HTTPX::Request::Body:#{object_id} " \
|
|
98
|
+
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
|
99
|
+
end
|
|
100
|
+
# :nocov:
|
|
101
|
+
|
|
102
|
+
private
|
|
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
|
|
111
|
+
def initialize_body(options)
|
|
112
|
+
@body = if options.body
|
|
113
|
+
Transcoder::Body.encode(options.body)
|
|
114
|
+
elsif options.form
|
|
115
|
+
Transcoder::Form.encode(options.form)
|
|
116
|
+
elsif options.json
|
|
117
|
+
Transcoder::JSON.encode(options.json)
|
|
118
|
+
elsif options.xml
|
|
119
|
+
Transcoder::Xml.encode(options.xml)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
return unless @body && options.compress_request_body && @headers.key?("content-encoding")
|
|
123
|
+
|
|
124
|
+
@headers.get("content-encoding").each do |encoding|
|
|
125
|
+
@body = self.class.initialize_deflater_body(@body, encoding)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class << self
|
|
130
|
+
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
|
131
|
+
def initialize_deflater_body(body, encoding)
|
|
132
|
+
case encoding
|
|
133
|
+
when "gzip"
|
|
134
|
+
Transcoder::GZIP.encode(body)
|
|
135
|
+
when "deflate"
|
|
136
|
+
Transcoder::Deflate.encode(body)
|
|
137
|
+
when "identity"
|
|
138
|
+
body
|
|
139
|
+
else
|
|
140
|
+
body
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Wrapper yielder which can be used with functions which expect an IO writer.
|
|
147
|
+
class ProcIO
|
|
148
|
+
def initialize(block)
|
|
149
|
+
@block = block
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Implementation the IO write protocol, which yield the given chunk to +@block+.
|
|
153
|
+
def write(data)
|
|
154
|
+
@block.call(data.dup)
|
|
155
|
+
data.bytesize
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
data/lib/httpx/request.rb
CHANGED
|
@@ -4,22 +4,52 @@ require "delegate"
|
|
|
4
4
|
require "forwardable"
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
|
+
# Defines how an HTTP request is handled internally, both in terms of making attributes accessible,
|
|
8
|
+
# as well as maintaining the state machine which manages streaming the request onto the wire.
|
|
7
9
|
class Request
|
|
8
10
|
extend Forwardable
|
|
9
11
|
include Callbacks
|
|
10
12
|
using URIExtensions
|
|
11
13
|
|
|
14
|
+
# default value used for "user-agent" header, when not overridden.
|
|
12
15
|
USER_AGENT = "httpx.rb/#{VERSION}"
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
# the upcased string HTTP verb for this request.
|
|
18
|
+
attr_reader :verb
|
|
15
19
|
|
|
16
|
-
#
|
|
20
|
+
# the absolute URI object for this request.
|
|
21
|
+
attr_reader :uri
|
|
22
|
+
|
|
23
|
+
# an HTTPX::Headers object containing the request HTTP headers.
|
|
24
|
+
attr_reader :headers
|
|
25
|
+
|
|
26
|
+
# an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
|
|
27
|
+
attr_reader :body
|
|
28
|
+
|
|
29
|
+
# a symbol describing which frame is currently being flushed.
|
|
30
|
+
attr_reader :state
|
|
31
|
+
|
|
32
|
+
# an HTTPX::Options object containing request options.
|
|
33
|
+
attr_reader :options
|
|
34
|
+
|
|
35
|
+
# the corresponding HTTPX::Response object, when there is one.
|
|
36
|
+
attr_reader :response
|
|
37
|
+
|
|
38
|
+
# Exception raised during enumerable body writes.
|
|
17
39
|
attr_reader :drain_error
|
|
18
40
|
|
|
41
|
+
# The IP address from the peer server.
|
|
42
|
+
attr_accessor :peer_address
|
|
43
|
+
|
|
44
|
+
attr_writer :persistent
|
|
45
|
+
|
|
46
|
+
# will be +true+ when request body has been completely flushed.
|
|
19
47
|
def_delegator :@body, :empty?
|
|
20
48
|
|
|
49
|
+
# initializes the instance with the given +verb+, an absolute or relative +uri+, and the
|
|
50
|
+
# request options.
|
|
21
51
|
def initialize(verb, uri, options = {})
|
|
22
|
-
@verb = verb.to_s.
|
|
52
|
+
@verb = verb.to_s.upcase
|
|
23
53
|
@options = Options.new(options)
|
|
24
54
|
@uri = Utils.to_uri(uri)
|
|
25
55
|
if @uri.relative?
|
|
@@ -37,20 +67,30 @@ module HTTPX
|
|
|
37
67
|
|
|
38
68
|
@body = @options.request_body_class.new(@headers, @options)
|
|
39
69
|
@state = :idle
|
|
70
|
+
@response = nil
|
|
71
|
+
@peer_address = nil
|
|
72
|
+
@persistent = @options.persistent
|
|
40
73
|
end
|
|
41
74
|
|
|
75
|
+
# the read timeout defied for this requet.
|
|
42
76
|
def read_timeout
|
|
43
77
|
@options.timeout[:read_timeout]
|
|
44
78
|
end
|
|
45
79
|
|
|
80
|
+
# the write timeout defied for this requet.
|
|
46
81
|
def write_timeout
|
|
47
82
|
@options.timeout[:write_timeout]
|
|
48
83
|
end
|
|
49
84
|
|
|
85
|
+
# the request timeout defied for this requet.
|
|
50
86
|
def request_timeout
|
|
51
87
|
@options.timeout[:request_timeout]
|
|
52
88
|
end
|
|
53
89
|
|
|
90
|
+
def persistent?
|
|
91
|
+
@persistent
|
|
92
|
+
end
|
|
93
|
+
|
|
54
94
|
def trailers?
|
|
55
95
|
defined?(@trailers)
|
|
56
96
|
end
|
|
@@ -59,40 +99,45 @@ module HTTPX
|
|
|
59
99
|
@trailers ||= @options.headers_class.new
|
|
60
100
|
end
|
|
61
101
|
|
|
102
|
+
# returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
|
|
62
103
|
def interests
|
|
63
104
|
return :r if @state == :done || @state == :expect
|
|
64
105
|
|
|
65
106
|
:w
|
|
66
107
|
end
|
|
67
108
|
|
|
68
|
-
if RUBY_VERSION < "2.2"
|
|
69
|
-
URIParser = URI::DEFAULT_PARSER
|
|
70
|
-
|
|
71
|
-
def initialize_with_escape(verb, uri, options = {})
|
|
72
|
-
initialize_without_escape(verb, URIParser.escape(uri.to_s), options)
|
|
73
|
-
end
|
|
74
|
-
alias_method :initialize_without_escape, :initialize
|
|
75
|
-
alias_method :initialize, :initialize_with_escape
|
|
76
|
-
end
|
|
77
|
-
|
|
78
109
|
def merge_headers(h)
|
|
79
110
|
@headers = @headers.merge(h)
|
|
80
111
|
end
|
|
81
112
|
|
|
113
|
+
# the URI scheme of the request +uri+.
|
|
82
114
|
def scheme
|
|
83
115
|
@uri.scheme
|
|
84
116
|
end
|
|
85
117
|
|
|
118
|
+
# sets the +response+ on this request.
|
|
86
119
|
def response=(response)
|
|
87
120
|
return unless response
|
|
88
121
|
|
|
89
|
-
if response.is_a?(Response) && response.status
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
if response.is_a?(Response) && response.status < 200
|
|
123
|
+
# deal with informational responses
|
|
124
|
+
|
|
125
|
+
if response.status == 100 && @headers.key?("expect")
|
|
126
|
+
@informational_status = response.status
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# 103 Early Hints advertises resources in document to browsers.
|
|
131
|
+
# not very relevant for an HTTP client, discard.
|
|
132
|
+
return if response.status >= 103
|
|
92
133
|
end
|
|
134
|
+
|
|
93
135
|
@response = response
|
|
136
|
+
|
|
137
|
+
emit(:response_started, response)
|
|
94
138
|
end
|
|
95
139
|
|
|
140
|
+
# returnns the URI path of the request +uri+.
|
|
96
141
|
def path
|
|
97
142
|
path = uri.path.dup
|
|
98
143
|
path = +"" if path.nil?
|
|
@@ -101,33 +146,48 @@ module HTTPX
|
|
|
101
146
|
path
|
|
102
147
|
end
|
|
103
148
|
|
|
104
|
-
#
|
|
149
|
+
# returs the URI authority of the request.
|
|
150
|
+
#
|
|
151
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "google.com"
|
|
152
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
|
|
105
153
|
def authority
|
|
106
154
|
@uri.authority
|
|
107
155
|
end
|
|
108
156
|
|
|
109
|
-
#
|
|
157
|
+
# returs the URI origin of the request.
|
|
158
|
+
#
|
|
159
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
|
|
160
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
|
|
110
161
|
def origin
|
|
111
162
|
@uri.origin
|
|
112
163
|
end
|
|
113
164
|
|
|
165
|
+
# returs the URI query string of the request (when available).
|
|
166
|
+
#
|
|
167
|
+
# session.build_request("GET", "https://search.com").query #=> ""
|
|
168
|
+
# session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
|
|
169
|
+
# session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
|
|
170
|
+
# session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
|
|
114
171
|
def query
|
|
115
172
|
return @query if defined?(@query)
|
|
116
173
|
|
|
117
174
|
query = []
|
|
118
175
|
if (q = @options.params)
|
|
119
|
-
query << Transcoder.
|
|
176
|
+
query << Transcoder::Form.encode(q)
|
|
120
177
|
end
|
|
121
178
|
query << @uri.query if @uri.query
|
|
122
179
|
@query = query.join("&")
|
|
123
180
|
end
|
|
124
181
|
|
|
182
|
+
# consumes and returns the next available chunk of request body that can be sent
|
|
125
183
|
def drain_body
|
|
126
184
|
return nil if @body.nil?
|
|
127
185
|
|
|
128
186
|
@drainer ||= @body.each
|
|
129
|
-
chunk = @drainer.next
|
|
130
|
-
|
|
187
|
+
chunk = @drainer.next.dup
|
|
188
|
+
|
|
189
|
+
emit(:body_chunk, chunk)
|
|
190
|
+
chunk
|
|
131
191
|
rescue StopIteration
|
|
132
192
|
nil
|
|
133
193
|
rescue StandardError => e
|
|
@@ -138,101 +198,14 @@ module HTTPX
|
|
|
138
198
|
# :nocov:
|
|
139
199
|
def inspect
|
|
140
200
|
"#<HTTPX::Request:#{object_id} " \
|
|
141
|
-
"#{@verb
|
|
201
|
+
"#{@verb} " \
|
|
142
202
|
"#{uri} " \
|
|
143
203
|
"@headers=#{@headers} " \
|
|
144
204
|
"@body=#{@body}>"
|
|
145
205
|
end
|
|
146
206
|
# :nocov:
|
|
147
207
|
|
|
148
|
-
|
|
149
|
-
class << self
|
|
150
|
-
def new(_, options)
|
|
151
|
-
return options.body if options.body.is_a?(self)
|
|
152
|
-
|
|
153
|
-
super
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def initialize(headers, options)
|
|
158
|
-
@headers = headers
|
|
159
|
-
@body = if options.body
|
|
160
|
-
Transcoder.registry("body").encode(options.body)
|
|
161
|
-
elsif options.form
|
|
162
|
-
Transcoder.registry("form").encode(options.form)
|
|
163
|
-
elsif options.json
|
|
164
|
-
Transcoder.registry("json").encode(options.json)
|
|
165
|
-
elsif options.xml
|
|
166
|
-
Transcoder.registry("xml").encode(options.xml)
|
|
167
|
-
end
|
|
168
|
-
return if @body.nil?
|
|
169
|
-
|
|
170
|
-
@headers["content-type"] ||= @body.content_type
|
|
171
|
-
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
|
172
|
-
super(@body)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def each(&block)
|
|
176
|
-
return enum_for(__method__) unless block
|
|
177
|
-
return if @body.nil?
|
|
178
|
-
|
|
179
|
-
body = stream(@body)
|
|
180
|
-
if body.respond_to?(:read)
|
|
181
|
-
::IO.copy_stream(body, ProcIO.new(block))
|
|
182
|
-
elsif body.respond_to?(:each)
|
|
183
|
-
body.each(&block)
|
|
184
|
-
else
|
|
185
|
-
block[body.to_s]
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def rewind
|
|
190
|
-
return if empty?
|
|
191
|
-
|
|
192
|
-
@body.rewind if @body.respond_to?(:rewind)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def empty?
|
|
196
|
-
return true if @body.nil?
|
|
197
|
-
return false if chunked?
|
|
198
|
-
|
|
199
|
-
@body.bytesize.zero?
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def bytesize
|
|
203
|
-
return 0 if @body.nil?
|
|
204
|
-
|
|
205
|
-
@body.bytesize
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def stream(body)
|
|
209
|
-
encoded = body
|
|
210
|
-
encoded = Transcoder.registry("chunker").encode(body.enum_for(:each)) if chunked?
|
|
211
|
-
encoded
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def unbounded_body?
|
|
215
|
-
return @unbounded_body if defined?(@unbounded_body)
|
|
216
|
-
|
|
217
|
-
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def chunked?
|
|
221
|
-
@headers["transfer-encoding"] == "chunked"
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def chunk!
|
|
225
|
-
@headers.add("transfer-encoding", "chunked")
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# :nocov:
|
|
229
|
-
def inspect
|
|
230
|
-
"#<HTTPX::Request::Body:#{object_id} " \
|
|
231
|
-
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
|
232
|
-
end
|
|
233
|
-
# :nocov:
|
|
234
|
-
end
|
|
235
|
-
|
|
208
|
+
# moves on to the +nextstate+ of the request state machine (when all preconditions are met)
|
|
236
209
|
def transition(nextstate)
|
|
237
210
|
case nextstate
|
|
238
211
|
when :idle
|
|
@@ -267,19 +240,11 @@ module HTTPX
|
|
|
267
240
|
nil
|
|
268
241
|
end
|
|
269
242
|
|
|
243
|
+
# whether the request supports the 100-continue handshake and already processed the 100 response.
|
|
270
244
|
def expects?
|
|
271
245
|
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
|
272
246
|
end
|
|
273
|
-
|
|
274
|
-
class ProcIO
|
|
275
|
-
def initialize(block)
|
|
276
|
-
@block = block
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def write(data)
|
|
280
|
-
@block.call(data.dup)
|
|
281
|
-
data.bytesize
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
247
|
end
|
|
285
248
|
end
|
|
249
|
+
|
|
250
|
+
require_relative "request/body"
|
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -4,12 +4,12 @@ require "resolv"
|
|
|
4
4
|
require "uri"
|
|
5
5
|
require "cgi"
|
|
6
6
|
require "forwardable"
|
|
7
|
+
require "httpx/base64"
|
|
7
8
|
|
|
8
9
|
module HTTPX
|
|
9
10
|
class Resolver::HTTPS < Resolver::Resolver
|
|
10
11
|
extend Forwardable
|
|
11
12
|
using URIExtensions
|
|
12
|
-
using StringExtensions
|
|
13
13
|
|
|
14
14
|
module DNSExtensions
|
|
15
15
|
refine Resolv::DNS do
|
|
@@ -27,7 +27,7 @@ module HTTPX
|
|
|
27
27
|
use_get: false,
|
|
28
28
|
}.freeze
|
|
29
29
|
|
|
30
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close
|
|
30
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate
|
|
31
31
|
|
|
32
32
|
def initialize(_, options)
|
|
33
33
|
super
|
|
@@ -50,6 +50,7 @@ module HTTPX
|
|
|
50
50
|
if @uri_addresses.empty?
|
|
51
51
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
|
52
52
|
ex.set_backtrace(caller)
|
|
53
|
+
connection.force_reset
|
|
53
54
|
throw(:resolve_error, ex)
|
|
54
55
|
end
|
|
55
56
|
|
|
@@ -67,9 +68,10 @@ module HTTPX
|
|
|
67
68
|
def resolver_connection
|
|
68
69
|
@resolver_connection ||= @pool.find_connection(@uri, @options) || begin
|
|
69
70
|
@building_connection = true
|
|
70
|
-
connection = @options.connection_class.new(
|
|
71
|
+
connection = @options.connection_class.new(@uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
|
|
71
72
|
@pool.init_connection(connection, @options)
|
|
72
|
-
|
|
73
|
+
# only explicity emit addresses if connection didn't pre-resolve, i.e. it's not an IP.
|
|
74
|
+
emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
|
|
73
75
|
@building_connection = false
|
|
74
76
|
connection
|
|
75
77
|
end
|
|
@@ -103,7 +105,7 @@ module HTTPX
|
|
|
103
105
|
resolver_connection.send(request)
|
|
104
106
|
@connections << connection
|
|
105
107
|
rescue ResolveError, Resolv::DNS::EncodeError => e
|
|
106
|
-
|
|
108
|
+
reset_hostname(hostname)
|
|
107
109
|
emit_resolve_error(connection, connection.origin.host, e)
|
|
108
110
|
end
|
|
109
111
|
end
|
|
@@ -112,7 +114,7 @@ module HTTPX
|
|
|
112
114
|
response.raise_for_status
|
|
113
115
|
rescue StandardError => e
|
|
114
116
|
hostname = @requests.delete(request)
|
|
115
|
-
connection =
|
|
117
|
+
connection = reset_hostname(hostname)
|
|
116
118
|
emit_resolve_error(connection, connection.origin.host, e)
|
|
117
119
|
else
|
|
118
120
|
# @type var response: HTTPX::Response
|
|
@@ -127,17 +129,40 @@ module HTTPX
|
|
|
127
129
|
end
|
|
128
130
|
|
|
129
131
|
def parse(request, response)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
code, result = decode_response_body(response)
|
|
133
|
+
|
|
134
|
+
case code
|
|
135
|
+
when :ok
|
|
136
|
+
parse_addresses(result, request)
|
|
137
|
+
when :no_domain_found
|
|
138
|
+
# Indicates no such domain was found.
|
|
139
|
+
|
|
140
|
+
host = @requests.delete(request)
|
|
141
|
+
connection = reset_hostname(host, reset_candidates: false)
|
|
142
|
+
|
|
143
|
+
unless @queries.value?(connection)
|
|
144
|
+
emit_resolve_error(connection)
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
resolve
|
|
149
|
+
when :dns_error
|
|
150
|
+
host = @requests.delete(request)
|
|
151
|
+
connection = reset_hostname(host)
|
|
152
|
+
|
|
153
|
+
emit_resolve_error(connection)
|
|
154
|
+
when :decode_error
|
|
155
|
+
host = @requests.delete(request)
|
|
156
|
+
connection = reset_hostname(host)
|
|
157
|
+
emit_resolve_error(connection, connection.origin.host, result)
|
|
137
158
|
end
|
|
138
|
-
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def parse_addresses(answers, request)
|
|
162
|
+
if answers.empty?
|
|
163
|
+
# no address found, eliminate candidates
|
|
139
164
|
host = @requests.delete(request)
|
|
140
|
-
connection =
|
|
165
|
+
connection = reset_hostname(host)
|
|
141
166
|
emit_resolve_error(connection)
|
|
142
167
|
return
|
|
143
168
|
|
|
@@ -148,7 +173,7 @@ module HTTPX
|
|
|
148
173
|
if address.key?("alias")
|
|
149
174
|
alias_address = answers[address["alias"]]
|
|
150
175
|
if alias_address.nil?
|
|
151
|
-
|
|
176
|
+
reset_hostname(address["name"])
|
|
152
177
|
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
|
|
153
178
|
@connections.delete(connection)
|
|
154
179
|
else
|
|
@@ -165,7 +190,7 @@ module HTTPX
|
|
|
165
190
|
next if addresses.empty?
|
|
166
191
|
|
|
167
192
|
hostname.delete_suffix!(".") if hostname.end_with?(".")
|
|
168
|
-
connection =
|
|
193
|
+
connection = reset_hostname(hostname, reset_candidates: false)
|
|
169
194
|
next unless connection # probably a retried query for which there's an answer
|
|
170
195
|
|
|
171
196
|
@connections.delete(connection)
|
|
@@ -210,5 +235,17 @@ module HTTPX
|
|
|
210
235
|
raise Error, "unsupported DNS mime-type (#{response.headers["content-type"]})"
|
|
211
236
|
end
|
|
212
237
|
end
|
|
238
|
+
|
|
239
|
+
def reset_hostname(hostname, reset_candidates: true)
|
|
240
|
+
connection = @queries.delete(hostname)
|
|
241
|
+
|
|
242
|
+
return connection unless connection && reset_candidates
|
|
243
|
+
|
|
244
|
+
# eliminate other candidates
|
|
245
|
+
candidates = @queries.select { |_, conn| connection == conn }.keys
|
|
246
|
+
@queries.delete_if { |h, _| candidates.include?(h) }
|
|
247
|
+
|
|
248
|
+
connection
|
|
249
|
+
end
|
|
213
250
|
end
|
|
214
251
|
end
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -46,14 +46,15 @@ module HTTPX
|
|
|
46
46
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
|
47
47
|
return unless addresses
|
|
48
48
|
|
|
49
|
-
addresses
|
|
49
|
+
addresses.group_by(&:family).sort { |(f1, _), (f2, _)| f2 <=> f1 }.each do |family, addrs|
|
|
50
|
+
# try to match the resolver by family. However, there are cases where that's not possible, as when
|
|
51
|
+
# the system does not have IPv6 connectivity, but it does support IPv6 via loopback/link-local.
|
|
52
|
+
resolver = @resolvers.find { |r| r.family == family } || @resolvers.first
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
addrs = addresses[resolver.family]
|
|
54
|
+
next unless resolver # this should ever happen
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
resolver.emit_addresses(connection, resolver.family, addrs)
|
|
56
|
+
# it does not matter which resolver it is, as early-resolve code is shared.
|
|
57
|
+
resolver.emit_addresses(connection, family, addrs, true)
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
@@ -64,12 +65,7 @@ module HTTPX
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def on_resolver_error(connection, error)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return unless @errors[connection].size >= @resolvers.size
|
|
70
|
-
|
|
71
|
-
errors = @errors.delete(connection)
|
|
72
|
-
emit(:error, connection, errors.first)
|
|
68
|
+
emit(:error, connection, error)
|
|
73
69
|
end
|
|
74
70
|
|
|
75
71
|
def on_resolver_close(resolver)
|