httpx 1.0.2 → 1.1.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/README.md +2 -2
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/lib/httpx/adapters/faraday.rb +28 -19
- data/lib/httpx/connection/http1.rb +13 -6
- data/lib/httpx/connection/http2.rb +1 -1
- data/lib/httpx/connection.rb +53 -15
- 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/plugins/proxy/http.rb +0 -1
- data/lib/httpx/pool.rb +0 -4
- 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/resolver.rb +5 -2
- data/lib/httpx/resolver/system.rb +5 -2
- data/lib/httpx/resolver.rb +6 -4
- 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 +64 -2
- data/lib/httpx/timers.rb +35 -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 +3 -3
- 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 +8 -2
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
|
data/lib/httpx/request.rb
CHANGED
|
@@ -4,20 +4,50 @@ 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
52
|
@verb = verb.to_s.upcase
|
|
23
53
|
@options = Options.new(options)
|
|
@@ -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,6 +99,7 @@ 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
|
|
|
@@ -69,10 +110,12 @@ module HTTPX
|
|
|
69
110
|
@headers = @headers.merge(h)
|
|
70
111
|
end
|
|
71
112
|
|
|
113
|
+
# the URI scheme of the request +uri+.
|
|
72
114
|
def scheme
|
|
73
115
|
@uri.scheme
|
|
74
116
|
end
|
|
75
117
|
|
|
118
|
+
# sets the +response+ on this request.
|
|
76
119
|
def response=(response)
|
|
77
120
|
return unless response
|
|
78
121
|
|
|
@@ -85,6 +128,7 @@ module HTTPX
|
|
|
85
128
|
emit(:response_started, response)
|
|
86
129
|
end
|
|
87
130
|
|
|
131
|
+
# returnns the URI path of the request +uri+.
|
|
88
132
|
def path
|
|
89
133
|
path = uri.path.dup
|
|
90
134
|
path = +"" if path.nil?
|
|
@@ -93,16 +137,28 @@ module HTTPX
|
|
|
93
137
|
path
|
|
94
138
|
end
|
|
95
139
|
|
|
96
|
-
#
|
|
140
|
+
# returs the URI authority of the request.
|
|
141
|
+
#
|
|
142
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "google.com"
|
|
143
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
|
|
97
144
|
def authority
|
|
98
145
|
@uri.authority
|
|
99
146
|
end
|
|
100
147
|
|
|
101
|
-
#
|
|
148
|
+
# returs the URI origin of the request.
|
|
149
|
+
#
|
|
150
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
|
|
151
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
|
|
102
152
|
def origin
|
|
103
153
|
@uri.origin
|
|
104
154
|
end
|
|
105
155
|
|
|
156
|
+
# returs the URI query string of the request (when available).
|
|
157
|
+
#
|
|
158
|
+
# session.build_request("GET", "https://search.com").query #=> ""
|
|
159
|
+
# session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
|
|
160
|
+
# session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
|
|
161
|
+
# session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
|
|
106
162
|
def query
|
|
107
163
|
return @query if defined?(@query)
|
|
108
164
|
|
|
@@ -114,6 +170,7 @@ module HTTPX
|
|
|
114
170
|
@query = query.join("&")
|
|
115
171
|
end
|
|
116
172
|
|
|
173
|
+
# consumes and returns the next available chunk of request body that can be sent
|
|
117
174
|
def drain_body
|
|
118
175
|
return nil if @body.nil?
|
|
119
176
|
|
|
@@ -139,6 +196,7 @@ module HTTPX
|
|
|
139
196
|
end
|
|
140
197
|
# :nocov:
|
|
141
198
|
|
|
199
|
+
# moves on to the +nextstate+ of the request state machine (when all preconditions are met)
|
|
142
200
|
def transition(nextstate)
|
|
143
201
|
case nextstate
|
|
144
202
|
when :idle
|
|
@@ -173,6 +231,7 @@ module HTTPX
|
|
|
173
231
|
nil
|
|
174
232
|
end
|
|
175
233
|
|
|
234
|
+
# whether the request supports the 100-continue handshake and already processed the 100 response.
|
|
176
235
|
def expects?
|
|
177
236
|
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
|
178
237
|
end
|
|
@@ -30,7 +30,7 @@ module HTTPX
|
|
|
30
30
|
nameserver = nameserver[family] if nameserver.is_a?(Hash)
|
|
31
31
|
Array(nameserver)
|
|
32
32
|
end
|
|
33
|
-
@ndots = @resolver_options
|
|
33
|
+
@ndots = @resolver_options.fetch(:ndots, 1)
|
|
34
34
|
@search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
|
|
35
35
|
@_timeouts = Array(@resolver_options[:timeouts])
|
|
36
36
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
|
@@ -103,7 +103,7 @@ module HTTPX
|
|
|
103
103
|
@timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
def
|
|
106
|
+
def handle_socket_timeout(interval)
|
|
107
107
|
do_retry(interval)
|
|
108
108
|
end
|
|
109
109
|
|
|
@@ -62,8 +62,11 @@ module HTTPX
|
|
|
62
62
|
addresses.first.to_s != connection.origin.host.to_s
|
|
63
63
|
log { "resolver: A response, applying resolution delay..." }
|
|
64
64
|
@pool.after(0.05) do
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
unless connection.state == :closed ||
|
|
66
|
+
# double emission check
|
|
67
|
+
(connection.addresses && addresses.intersect?(connection.addresses))
|
|
68
|
+
emit_resolved_connection(connection, addresses)
|
|
69
|
+
end
|
|
67
70
|
end
|
|
68
71
|
else
|
|
69
72
|
emit_resolved_connection(connection, addresses)
|
|
@@ -92,7 +92,7 @@ module HTTPX
|
|
|
92
92
|
resolve
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
def
|
|
95
|
+
def handle_socket_timeout(interval)
|
|
96
96
|
error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
|
|
97
97
|
error.set_backtrace(caller)
|
|
98
98
|
on_error(error)
|
|
@@ -164,7 +164,8 @@ module HTTPX
|
|
|
164
164
|
def async_resolve(connection, hostname, scheme)
|
|
165
165
|
families = connection.options.ip_families
|
|
166
166
|
log { "resolver: query for #{hostname}" }
|
|
167
|
-
|
|
167
|
+
timeouts = @timeouts[connection.origin.host]
|
|
168
|
+
resolve_timeout = timeouts.first
|
|
168
169
|
|
|
169
170
|
Thread.start do
|
|
170
171
|
Thread.current.report_on_exception = false
|
|
@@ -191,6 +192,8 @@ module HTTPX
|
|
|
191
192
|
end
|
|
192
193
|
rescue StandardError => e
|
|
193
194
|
if e.is_a?(Timeout::Error)
|
|
195
|
+
timeouts.shift
|
|
196
|
+
retry unless timeouts.empty?
|
|
194
197
|
e = ResolveTimeoutError.new(resolve_timeout, e.message)
|
|
195
198
|
e.set_backtrace(e.backtrace)
|
|
196
199
|
end
|
data/lib/httpx/resolver.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "ipaddr"
|
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
7
|
module Resolver
|
|
8
|
-
RESOLVE_TIMEOUT =
|
|
8
|
+
RESOLVE_TIMEOUT = [2, 3].freeze
|
|
9
9
|
|
|
10
10
|
require "httpx/resolver/resolver"
|
|
11
11
|
require "httpx/resolver/system"
|
|
@@ -87,16 +87,18 @@ module HTTPX
|
|
|
87
87
|
def lookup(hostname, ttl)
|
|
88
88
|
return unless @lookups.key?(hostname)
|
|
89
89
|
|
|
90
|
-
@lookups[hostname] = @lookups[hostname].select do |address|
|
|
90
|
+
entries = @lookups[hostname] = @lookups[hostname].select do |address|
|
|
91
91
|
address["TTL"] > ttl
|
|
92
92
|
end
|
|
93
|
-
|
|
93
|
+
|
|
94
|
+
ips = entries.flat_map do |address|
|
|
94
95
|
if address.key?("alias")
|
|
95
96
|
lookup(address["alias"], ttl)
|
|
96
97
|
else
|
|
97
98
|
IPAddr.new(address["data"])
|
|
98
99
|
end
|
|
99
|
-
end
|
|
100
|
+
end.compact
|
|
101
|
+
|
|
100
102
|
ips unless ips.empty?
|
|
101
103
|
end
|
|
102
104
|
|
data/lib/httpx/response/body.rb
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
+
# Implementation of the HTTP Response body as a buffer which implements the IO writer protocol
|
|
5
|
+
# (for buffering the response payload), the IO reader protocol (for consuming the response payload),
|
|
6
|
+
# and can be iterated over (via #each, which yields the payload in chunks).
|
|
4
7
|
class Response::Body
|
|
5
|
-
|
|
8
|
+
# the payload encoding (i.e. "utf-8", "ASCII-8BIT")
|
|
9
|
+
attr_reader :encoding
|
|
6
10
|
|
|
11
|
+
# Array of encodings contained in the response "content-encoding" header.
|
|
12
|
+
attr_reader :encodings
|
|
13
|
+
|
|
14
|
+
# initialized with the corresponding HTTPX::Response +response+ and HTTPX::Options +options+.
|
|
7
15
|
def initialize(response, options)
|
|
8
16
|
@response = response
|
|
9
17
|
@headers = response.headers
|
|
10
18
|
@options = options
|
|
11
|
-
@threshold_size = options.body_threshold_size
|
|
12
19
|
@window_size = options.window_size
|
|
13
20
|
@encoding = response.content_type.charset || Encoding::BINARY
|
|
14
21
|
@encodings = []
|
|
15
22
|
@length = 0
|
|
16
23
|
@buffer = nil
|
|
24
|
+
@reader = nil
|
|
17
25
|
@state = :idle
|
|
18
26
|
initialize_inflaters
|
|
19
27
|
end
|
|
@@ -28,6 +36,8 @@ module HTTPX
|
|
|
28
36
|
@state == :closed
|
|
29
37
|
end
|
|
30
38
|
|
|
39
|
+
# write the response payload +chunk+ into the buffer. Inflates the chunk when required
|
|
40
|
+
# and supported.
|
|
31
41
|
def write(chunk)
|
|
32
42
|
return if @state == :closed
|
|
33
43
|
|
|
@@ -44,6 +54,7 @@ module HTTPX
|
|
|
44
54
|
size
|
|
45
55
|
end
|
|
46
56
|
|
|
57
|
+
# reads a chunk from the payload (implementation of the IO reader protocol).
|
|
47
58
|
def read(*args)
|
|
48
59
|
return unless @buffer
|
|
49
60
|
|
|
@@ -55,10 +66,13 @@ module HTTPX
|
|
|
55
66
|
@reader.read(*args)
|
|
56
67
|
end
|
|
57
68
|
|
|
69
|
+
# size of the decoded response payload. May differ from "content-length" header if
|
|
70
|
+
# response was encoded over-the-wire.
|
|
58
71
|
def bytesize
|
|
59
72
|
@length
|
|
60
73
|
end
|
|
61
74
|
|
|
75
|
+
# yields the payload in chunks.
|
|
62
76
|
def each
|
|
63
77
|
return enum_for(__method__) unless block_given?
|
|
64
78
|
|
|
@@ -74,12 +88,14 @@ module HTTPX
|
|
|
74
88
|
end
|
|
75
89
|
end
|
|
76
90
|
|
|
91
|
+
# returns the declared filename in the "contennt-disposition" header, when present.
|
|
77
92
|
def filename
|
|
78
93
|
return unless @headers.key?("content-disposition")
|
|
79
94
|
|
|
80
95
|
Utils.get_filename(@headers["content-disposition"])
|
|
81
96
|
end
|
|
82
97
|
|
|
98
|
+
# returns the full response payload as a string.
|
|
83
99
|
def to_s
|
|
84
100
|
return "".b unless @buffer
|
|
85
101
|
|
|
@@ -88,10 +104,16 @@ module HTTPX
|
|
|
88
104
|
|
|
89
105
|
alias_method :to_str, :to_s
|
|
90
106
|
|
|
107
|
+
# whether the payload is empty.
|
|
91
108
|
def empty?
|
|
92
109
|
@length.zero?
|
|
93
110
|
end
|
|
94
111
|
|
|
112
|
+
# copies the payload to +dest+.
|
|
113
|
+
#
|
|
114
|
+
# body.copy_to("path/to/file")
|
|
115
|
+
# body.copy_to(Pathname.new("path/to/file"))
|
|
116
|
+
# body.copy_to(File.new("path/to/file"))
|
|
95
117
|
def copy_to(dest)
|
|
96
118
|
return unless @buffer
|
|
97
119
|
|
|
@@ -132,6 +154,7 @@ module HTTPX
|
|
|
132
154
|
end
|
|
133
155
|
# :nocov:
|
|
134
156
|
|
|
157
|
+
# rewinds the response payload buffer.
|
|
135
158
|
def rewind
|
|
136
159
|
return unless @buffer
|
|
137
160
|
|
|
@@ -144,6 +167,8 @@ module HTTPX
|
|
|
144
167
|
private
|
|
145
168
|
|
|
146
169
|
def initialize_inflaters
|
|
170
|
+
@inflaters = nil
|
|
171
|
+
|
|
147
172
|
return unless @headers.key?("content-encoding")
|
|
148
173
|
|
|
149
174
|
return unless @options.decompress_response_body
|
|
@@ -168,7 +193,7 @@ module HTTPX
|
|
|
168
193
|
return unless @state == :idle
|
|
169
194
|
|
|
170
195
|
@buffer = Response::Buffer.new(
|
|
171
|
-
threshold_size: @
|
|
196
|
+
threshold_size: @options.body_threshold_size,
|
|
172
197
|
bytesize: @length,
|
|
173
198
|
encoding: @encoding
|
|
174
199
|
)
|
|
@@ -179,7 +204,7 @@ module HTTPX
|
|
|
179
204
|
@state = nextstate
|
|
180
205
|
end
|
|
181
206
|
|
|
182
|
-
def _with_same_buffer_pos
|
|
207
|
+
def _with_same_buffer_pos # :nodoc:
|
|
183
208
|
return yield unless @buffer && @buffer.respond_to?(:pos)
|
|
184
209
|
|
|
185
210
|
# @type ivar @buffer: StringIO | Tempfile
|
|
@@ -193,7 +218,7 @@ module HTTPX
|
|
|
193
218
|
end
|
|
194
219
|
|
|
195
220
|
class << self
|
|
196
|
-
def initialize_inflater_by_encoding(encoding, response, **kwargs)
|
|
221
|
+
def initialize_inflater_by_encoding(encoding, response, **kwargs) # :nodoc:
|
|
197
222
|
case encoding
|
|
198
223
|
when "gzip"
|
|
199
224
|
Transcoder::GZIP.decode(response, **kwargs)
|
|
@@ -5,12 +5,15 @@ require "stringio"
|
|
|
5
5
|
require "tempfile"
|
|
6
6
|
|
|
7
7
|
module HTTPX
|
|
8
|
+
# wraps and delegates to an internal buffer, which can be a StringIO or a Tempfile.
|
|
8
9
|
class Response::Buffer < SimpleDelegator
|
|
10
|
+
# initializes buffer with the +threshold_size+ over which the payload gets buffer to a tempfile,
|
|
11
|
+
# the initial +bytesize+, and the +encoding+.
|
|
9
12
|
def initialize(threshold_size:, bytesize: 0, encoding: Encoding::BINARY)
|
|
10
13
|
@threshold_size = threshold_size
|
|
11
14
|
@bytesize = bytesize
|
|
12
15
|
@encoding = encoding
|
|
13
|
-
|
|
16
|
+
@buffer = StringIO.new("".b)
|
|
14
17
|
super(@buffer)
|
|
15
18
|
end
|
|
16
19
|
|
|
@@ -20,16 +23,19 @@ module HTTPX
|
|
|
20
23
|
@buffer = other.instance_variable_get(:@buffer).dup
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
# size in bytes of the buffered content.
|
|
23
27
|
def size
|
|
24
28
|
@bytesize
|
|
25
29
|
end
|
|
26
30
|
|
|
31
|
+
# writes the +chunk+ into the buffer.
|
|
27
32
|
def write(chunk)
|
|
28
33
|
@bytesize += chunk.bytesize
|
|
29
34
|
try_upgrade_buffer
|
|
30
35
|
@buffer.write(chunk)
|
|
31
36
|
end
|
|
32
37
|
|
|
38
|
+
# returns the buffered content as a string.
|
|
33
39
|
def to_s
|
|
34
40
|
case @buffer
|
|
35
41
|
when StringIO
|
|
@@ -49,6 +55,7 @@ module HTTPX
|
|
|
49
55
|
end
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
# closes the buffer.
|
|
52
59
|
def close
|
|
53
60
|
@buffer.close
|
|
54
61
|
@buffer.unlink if @buffer.respond_to?(:unlink)
|
|
@@ -56,28 +63,27 @@ module HTTPX
|
|
|
56
63
|
|
|
57
64
|
private
|
|
58
65
|
|
|
66
|
+
# initializes the buffer into a StringIO, or turns it into a Tempfile when the threshold
|
|
67
|
+
# has been reached.
|
|
59
68
|
def try_upgrade_buffer
|
|
60
|
-
|
|
61
|
-
aux = @buffer
|
|
62
|
-
|
|
63
|
-
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
|
69
|
+
return unless @bytesize > @threshold_size
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
aux.rewind
|
|
67
|
-
::IO.copy_stream(aux, @buffer)
|
|
68
|
-
aux.close
|
|
69
|
-
end
|
|
71
|
+
return if @buffer.is_a?(Tempfile)
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
return if @buffer
|
|
73
|
+
aux = @buffer
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
|
75
76
|
|
|
77
|
+
if aux
|
|
78
|
+
aux.rewind
|
|
79
|
+
::IO.copy_stream(aux, @buffer)
|
|
80
|
+
aux.close
|
|
76
81
|
end
|
|
82
|
+
|
|
77
83
|
__setobj__(@buffer)
|
|
78
84
|
end
|
|
79
85
|
|
|
80
|
-
def _with_same_buffer_pos
|
|
86
|
+
def _with_same_buffer_pos # :nodoc:
|
|
81
87
|
current_pos = @buffer.pos
|
|
82
88
|
@buffer.rewind
|
|
83
89
|
begin
|