httpx 0.20.0 → 1.3.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 +5 -5
- 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_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- 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/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- 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 +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -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 +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- 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 +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- 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 +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- 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 +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- 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 +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -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 +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- 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 +52 -0
- data/lib/httpx/transcoder.rb +5 -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 +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- 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 +71 -0
- data/sig/plugins/compression.rbs +7 -5
- 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 +18 -4
- 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 +7 -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/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- 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 +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- 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 +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- 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 +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- 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 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
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, body: nil, **params)
|
|
8
|
+
if body.is_a?(self)
|
|
9
|
+
# request derives its options from body
|
|
10
|
+
body.options = options.merge(params)
|
|
11
|
+
return body
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_accessor :options
|
|
19
|
+
|
|
20
|
+
# inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
|
|
21
|
+
# it wraps the given body with the appropriate encoder on initialization.
|
|
22
|
+
#
|
|
23
|
+
# ..., json: { foo: "bar" }) #=> json encoder
|
|
24
|
+
# ..., form: { foo: "bar" }) #=> form urlencoded encoder
|
|
25
|
+
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
|
26
|
+
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
|
27
|
+
# ..., form: { body: "bla") }) #=> raw data encoder
|
|
28
|
+
def initialize(headers, options, body: nil, form: nil, json: nil, xml: nil, **params)
|
|
29
|
+
@headers = headers
|
|
30
|
+
@options = options.merge(params)
|
|
31
|
+
|
|
32
|
+
@body = if body
|
|
33
|
+
Transcoder::Body.encode(body)
|
|
34
|
+
elsif form
|
|
35
|
+
Transcoder::Form.encode(form)
|
|
36
|
+
elsif json
|
|
37
|
+
Transcoder::JSON.encode(json)
|
|
38
|
+
elsif xml
|
|
39
|
+
Transcoder::Xml.encode(xml)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if @body
|
|
43
|
+
if @options.compress_request_body && @headers.key?("content-encoding")
|
|
44
|
+
|
|
45
|
+
@headers.get("content-encoding").each do |encoding|
|
|
46
|
+
@body = self.class.initialize_deflater_body(@body, encoding)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@headers["content-type"] ||= @body.content_type
|
|
51
|
+
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
super(@body)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# consumes and yields the request payload in chunks.
|
|
58
|
+
def each(&block)
|
|
59
|
+
return enum_for(__method__) unless block
|
|
60
|
+
return if @body.nil?
|
|
61
|
+
|
|
62
|
+
body = stream(@body)
|
|
63
|
+
if body.respond_to?(:read)
|
|
64
|
+
::IO.copy_stream(body, ProcIO.new(block))
|
|
65
|
+
elsif body.respond_to?(:each)
|
|
66
|
+
body.each(&block)
|
|
67
|
+
else
|
|
68
|
+
block[body.to_s]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# if the +@body+ is rewindable, it rewinnds it.
|
|
73
|
+
def rewind
|
|
74
|
+
return if empty?
|
|
75
|
+
|
|
76
|
+
@body.rewind if @body.respond_to?(:rewind)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# return +true+ if the +body+ has been fully drained (or does nnot exist).
|
|
80
|
+
def empty?
|
|
81
|
+
return true if @body.nil?
|
|
82
|
+
return false if chunked?
|
|
83
|
+
|
|
84
|
+
@body.bytesize.zero?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# returns the +@body+ payload size in bytes.
|
|
88
|
+
def bytesize
|
|
89
|
+
return 0 if @body.nil?
|
|
90
|
+
|
|
91
|
+
@body.bytesize
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# sets the body to yield using chunked trannsfer encoding format.
|
|
95
|
+
def stream(body)
|
|
96
|
+
return body unless chunked?
|
|
97
|
+
|
|
98
|
+
Transcoder::Chunker.encode(body.enum_for(:each))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# returns whether the body yields infinitely.
|
|
102
|
+
def unbounded_body?
|
|
103
|
+
return @unbounded_body if defined?(@unbounded_body)
|
|
104
|
+
|
|
105
|
+
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# returns whether the chunked transfer encoding header is set.
|
|
109
|
+
def chunked?
|
|
110
|
+
@headers["transfer-encoding"] == "chunked"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# sets the chunked transfer encoding header.
|
|
114
|
+
def chunk!
|
|
115
|
+
@headers.add("transfer-encoding", "chunked")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# :nocov:
|
|
119
|
+
def inspect
|
|
120
|
+
"#<HTTPX::Request::Body:#{object_id} " \
|
|
121
|
+
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
|
122
|
+
end
|
|
123
|
+
# :nocov:
|
|
124
|
+
|
|
125
|
+
class << self
|
|
126
|
+
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
|
127
|
+
def initialize_deflater_body(body, encoding)
|
|
128
|
+
case encoding
|
|
129
|
+
when "gzip"
|
|
130
|
+
Transcoder::GZIP.encode(body)
|
|
131
|
+
when "deflate"
|
|
132
|
+
Transcoder::Deflate.encode(body)
|
|
133
|
+
when "identity"
|
|
134
|
+
body
|
|
135
|
+
else
|
|
136
|
+
body
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Wrapper yielder which can be used with functions which expect an IO writer.
|
|
143
|
+
class ProcIO
|
|
144
|
+
def initialize(block)
|
|
145
|
+
@block = block
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Implementation the IO write protocol, which yield the given chunk to +@block+.
|
|
149
|
+
def write(data)
|
|
150
|
+
@block.call(data.dup)
|
|
151
|
+
data.bytesize
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
data/lib/httpx/request.rb
CHANGED
|
@@ -4,47 +4,85 @@ 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
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:options, :get, :head, :post, :put, :delete, :trace, :connect,
|
|
15
|
-
|
|
16
|
-
# RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
|
|
17
|
-
:propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock,
|
|
14
|
+
# default value used for "user-agent" header, when not overridden.
|
|
15
|
+
USER_AGENT = "httpx.rb/#{VERSION}"
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# the upcased string HTTP verb for this request.
|
|
18
|
+
attr_reader :verb
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
# the absolute URI object for this request.
|
|
21
|
+
attr_reader :uri
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
# an HTTPX::Headers object containing the request HTTP headers.
|
|
24
|
+
attr_reader :headers
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
# an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
|
|
27
|
+
attr_reader :body
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
].freeze
|
|
29
|
+
# a symbol describing which frame is currently being flushed.
|
|
30
|
+
attr_reader :state
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
# an HTTPX::Options object containing request options.
|
|
33
|
+
attr_reader :options
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
# the corresponding HTTPX::Response object, when there is one.
|
|
36
|
+
attr_reader :response
|
|
38
37
|
|
|
39
|
-
# Exception raised during enumerable body writes
|
|
38
|
+
# Exception raised during enumerable body writes.
|
|
40
39
|
attr_reader :drain_error
|
|
41
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.
|
|
42
47
|
def_delegator :@body, :empty?
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
# initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
|
|
50
|
+
# an absolute or relative +uri+ (either as String or URI::HTTP object), the
|
|
51
|
+
# request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
|
|
52
|
+
#
|
|
53
|
+
# Besides any of the options documented in HTTPX::Options (which would override or merge with what
|
|
54
|
+
# +options+ sets), it accepts also the following:
|
|
55
|
+
#
|
|
56
|
+
# :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
|
|
57
|
+
# :body :: to be encoded in the request body payload. can be a String, an IO object (i.e. a File), or an Enumerable.
|
|
58
|
+
# :form :: hash of array of key-values which will be form-urlencoded- or multipart-encoded in requests body payload.
|
|
59
|
+
# :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
|
|
60
|
+
# :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
|
|
61
|
+
#
|
|
62
|
+
# :body, :form, :json and :xml are all mutually exclusive, i.e. only one of them gets picked up.
|
|
63
|
+
def initialize(verb, uri, options, params = EMPTY_HASH)
|
|
64
|
+
@verb = verb.to_s.upcase
|
|
47
65
|
@uri = Utils.to_uri(uri)
|
|
66
|
+
|
|
67
|
+
@headers = options.headers.dup
|
|
68
|
+
merge_headers(params.delete(:headers)) if params.key?(:headers)
|
|
69
|
+
|
|
70
|
+
@headers["user-agent"] ||= USER_AGENT
|
|
71
|
+
@headers["accept"] ||= "*/*"
|
|
72
|
+
|
|
73
|
+
# forego compression in the Range request case
|
|
74
|
+
if @headers.key?("range")
|
|
75
|
+
@headers.delete("accept-encoding")
|
|
76
|
+
else
|
|
77
|
+
@headers["accept-encoding"] ||= options.supported_compression_formats
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@query_params = params.delete(:params) if params.key?(:params)
|
|
81
|
+
|
|
82
|
+
@body = options.request_body_class.new(@headers, options, **params)
|
|
83
|
+
|
|
84
|
+
@options = @body.options
|
|
85
|
+
|
|
48
86
|
if @uri.relative?
|
|
49
87
|
origin = @options.origin
|
|
50
88
|
raise(Error, "invalid URI: #{@uri}") unless origin
|
|
@@ -54,92 +92,131 @@ module HTTPX
|
|
|
54
92
|
@uri = origin.merge("#{base_path}#{@uri}")
|
|
55
93
|
end
|
|
56
94
|
|
|
57
|
-
|
|
95
|
+
@state = :idle
|
|
96
|
+
@response = nil
|
|
97
|
+
@peer_address = nil
|
|
98
|
+
@persistent = @options.persistent
|
|
99
|
+
end
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@
|
|
101
|
+
# the read timeout defined for this requet.
|
|
102
|
+
def read_timeout
|
|
103
|
+
@options.timeout[:read_timeout]
|
|
104
|
+
end
|
|
62
105
|
|
|
63
|
-
|
|
64
|
-
|
|
106
|
+
# the write timeout defined for this requet.
|
|
107
|
+
def write_timeout
|
|
108
|
+
@options.timeout[:write_timeout]
|
|
65
109
|
end
|
|
66
110
|
|
|
111
|
+
# the request timeout defined for this requet.
|
|
112
|
+
def request_timeout
|
|
113
|
+
@options.timeout[:request_timeout]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def persistent?
|
|
117
|
+
@persistent
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# if the request contains trailer headers
|
|
67
121
|
def trailers?
|
|
68
122
|
defined?(@trailers)
|
|
69
123
|
end
|
|
70
124
|
|
|
125
|
+
# returns an instance of HTTPX::Headers containing the trailer headers
|
|
71
126
|
def trailers
|
|
72
127
|
@trailers ||= @options.headers_class.new
|
|
73
128
|
end
|
|
74
129
|
|
|
130
|
+
# returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
|
|
75
131
|
def interests
|
|
76
132
|
return :r if @state == :done || @state == :expect
|
|
77
133
|
|
|
78
134
|
:w
|
|
79
135
|
end
|
|
80
136
|
|
|
81
|
-
|
|
82
|
-
URIParser = URI::DEFAULT_PARSER
|
|
83
|
-
|
|
84
|
-
def initialize_with_escape(verb, uri, options = {})
|
|
85
|
-
initialize_without_escape(verb, URIParser.escape(uri.to_s), options)
|
|
86
|
-
end
|
|
87
|
-
alias_method :initialize_without_escape, :initialize
|
|
88
|
-
alias_method :initialize, :initialize_with_escape
|
|
89
|
-
end
|
|
90
|
-
|
|
137
|
+
# merges +h+ into the instance of HTTPX::Headers of the request.
|
|
91
138
|
def merge_headers(h)
|
|
92
139
|
@headers = @headers.merge(h)
|
|
93
140
|
end
|
|
94
141
|
|
|
142
|
+
# the URI scheme of the request +uri+.
|
|
95
143
|
def scheme
|
|
96
144
|
@uri.scheme
|
|
97
145
|
end
|
|
98
146
|
|
|
147
|
+
# sets the +response+ on this request.
|
|
99
148
|
def response=(response)
|
|
100
149
|
return unless response
|
|
101
150
|
|
|
102
|
-
if response.is_a?(Response) && response.status
|
|
103
|
-
|
|
104
|
-
|
|
151
|
+
if response.is_a?(Response) && response.status < 200
|
|
152
|
+
# deal with informational responses
|
|
153
|
+
|
|
154
|
+
if response.status == 100 && @headers.key?("expect")
|
|
155
|
+
@informational_status = response.status
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# 103 Early Hints advertises resources in document to browsers.
|
|
160
|
+
# not very relevant for an HTTP client, discard.
|
|
161
|
+
return if response.status >= 103
|
|
105
162
|
end
|
|
163
|
+
|
|
106
164
|
@response = response
|
|
165
|
+
|
|
166
|
+
emit(:response_started, response)
|
|
107
167
|
end
|
|
108
168
|
|
|
169
|
+
# returnns the URI path of the request +uri+.
|
|
109
170
|
def path
|
|
110
171
|
path = uri.path.dup
|
|
172
|
+
path = +"" if path.nil?
|
|
111
173
|
path << "/" if path.empty?
|
|
112
174
|
path << "?#{query}" unless query.empty?
|
|
113
175
|
path
|
|
114
176
|
end
|
|
115
177
|
|
|
116
|
-
#
|
|
178
|
+
# returs the URI authority of the request.
|
|
179
|
+
#
|
|
180
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "google.com"
|
|
181
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
|
|
117
182
|
def authority
|
|
118
183
|
@uri.authority
|
|
119
184
|
end
|
|
120
185
|
|
|
121
|
-
#
|
|
186
|
+
# returs the URI origin of the request.
|
|
187
|
+
#
|
|
188
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
|
|
189
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
|
|
122
190
|
def origin
|
|
123
191
|
@uri.origin
|
|
124
192
|
end
|
|
125
193
|
|
|
194
|
+
# returs the URI query string of the request (when available).
|
|
195
|
+
#
|
|
196
|
+
# session.build_request("GET", "https://search.com").query #=> ""
|
|
197
|
+
# session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
|
|
198
|
+
# session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
|
|
199
|
+
# session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
|
|
126
200
|
def query
|
|
127
201
|
return @query if defined?(@query)
|
|
128
202
|
|
|
129
203
|
query = []
|
|
130
|
-
if (q = @
|
|
131
|
-
query << Transcoder.
|
|
204
|
+
if (q = @query_params)
|
|
205
|
+
query << Transcoder::Form.encode(q)
|
|
132
206
|
end
|
|
133
207
|
query << @uri.query if @uri.query
|
|
134
208
|
@query = query.join("&")
|
|
135
209
|
end
|
|
136
210
|
|
|
211
|
+
# consumes and returns the next available chunk of request body that can be sent
|
|
137
212
|
def drain_body
|
|
138
213
|
return nil if @body.nil?
|
|
139
214
|
|
|
140
215
|
@drainer ||= @body.each
|
|
141
|
-
chunk = @drainer.next
|
|
142
|
-
|
|
216
|
+
chunk = @drainer.next.dup
|
|
217
|
+
|
|
218
|
+
emit(:body_chunk, chunk)
|
|
219
|
+
chunk
|
|
143
220
|
rescue StopIteration
|
|
144
221
|
nil
|
|
145
222
|
rescue StandardError => e
|
|
@@ -150,99 +227,14 @@ module HTTPX
|
|
|
150
227
|
# :nocov:
|
|
151
228
|
def inspect
|
|
152
229
|
"#<HTTPX::Request:#{object_id} " \
|
|
153
|
-
"#{@verb
|
|
230
|
+
"#{@verb} " \
|
|
154
231
|
"#{uri} " \
|
|
155
232
|
"@headers=#{@headers} " \
|
|
156
233
|
"@body=#{@body}>"
|
|
157
234
|
end
|
|
158
235
|
# :nocov:
|
|
159
236
|
|
|
160
|
-
|
|
161
|
-
class << self
|
|
162
|
-
def new(_, options)
|
|
163
|
-
return options.body if options.body.is_a?(self)
|
|
164
|
-
|
|
165
|
-
super
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def initialize(headers, options)
|
|
170
|
-
@headers = headers
|
|
171
|
-
@body = if options.body
|
|
172
|
-
Transcoder.registry("body").encode(options.body)
|
|
173
|
-
elsif options.form
|
|
174
|
-
Transcoder.registry("form").encode(options.form)
|
|
175
|
-
elsif options.json
|
|
176
|
-
Transcoder.registry("json").encode(options.json)
|
|
177
|
-
end
|
|
178
|
-
return if @body.nil?
|
|
179
|
-
|
|
180
|
-
@headers["content-type"] ||= @body.content_type
|
|
181
|
-
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
|
182
|
-
super(@body)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def each(&block)
|
|
186
|
-
return enum_for(__method__) unless block
|
|
187
|
-
return if @body.nil?
|
|
188
|
-
|
|
189
|
-
body = stream(@body)
|
|
190
|
-
if body.respond_to?(:read)
|
|
191
|
-
::IO.copy_stream(body, ProcIO.new(block))
|
|
192
|
-
elsif body.respond_to?(:each)
|
|
193
|
-
body.each(&block)
|
|
194
|
-
else
|
|
195
|
-
block[body.to_s]
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def rewind
|
|
200
|
-
return if empty?
|
|
201
|
-
|
|
202
|
-
@body.rewind if @body.respond_to?(:rewind)
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def empty?
|
|
206
|
-
return true if @body.nil?
|
|
207
|
-
return false if chunked?
|
|
208
|
-
|
|
209
|
-
@body.bytesize.zero?
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def bytesize
|
|
213
|
-
return 0 if @body.nil?
|
|
214
|
-
|
|
215
|
-
@body.bytesize
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def stream(body)
|
|
219
|
-
encoded = body
|
|
220
|
-
encoded = Transcoder.registry("chunker").encode(body.enum_for(:each)) if chunked?
|
|
221
|
-
encoded
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def unbounded_body?
|
|
225
|
-
return @unbounded_body if defined?(@unbounded_body)
|
|
226
|
-
|
|
227
|
-
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def chunked?
|
|
231
|
-
@headers["transfer-encoding"] == "chunked"
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def chunk!
|
|
235
|
-
@headers.add("transfer-encoding", "chunked")
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# :nocov:
|
|
239
|
-
def inspect
|
|
240
|
-
"#<HTTPX::Request::Body:#{object_id} " \
|
|
241
|
-
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
|
242
|
-
end
|
|
243
|
-
# :nocov:
|
|
244
|
-
end
|
|
245
|
-
|
|
237
|
+
# moves on to the +nextstate+ of the request state machine (when all preconditions are met)
|
|
246
238
|
def transition(nextstate)
|
|
247
239
|
case nextstate
|
|
248
240
|
when :idle
|
|
@@ -277,19 +269,11 @@ module HTTPX
|
|
|
277
269
|
nil
|
|
278
270
|
end
|
|
279
271
|
|
|
272
|
+
# whether the request supports the 100-continue handshake and already processed the 100 response.
|
|
280
273
|
def expects?
|
|
281
274
|
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
|
282
275
|
end
|
|
283
|
-
|
|
284
|
-
class ProcIO
|
|
285
|
-
def initialize(block)
|
|
286
|
-
@block = block
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def write(data)
|
|
290
|
-
@block.call(data.dup)
|
|
291
|
-
data.bytesize
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
276
|
end
|
|
295
277
|
end
|
|
278
|
+
|
|
279
|
+
require_relative "request/body"
|