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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/1_1_0.md +32 -0
  4. data/doc/release_notes/1_1_1.md +17 -0
  5. data/lib/httpx/adapters/faraday.rb +28 -19
  6. data/lib/httpx/connection/http1.rb +13 -6
  7. data/lib/httpx/connection/http2.rb +1 -1
  8. data/lib/httpx/connection.rb +53 -15
  9. data/lib/httpx/domain_name.rb +6 -2
  10. data/lib/httpx/errors.rb +32 -0
  11. data/lib/httpx/io/ssl.rb +3 -1
  12. data/lib/httpx/io/tcp.rb +4 -2
  13. data/lib/httpx/io.rb +5 -1
  14. data/lib/httpx/options.rb +48 -1
  15. data/lib/httpx/plugins/expect.rb +10 -8
  16. data/lib/httpx/plugins/proxy/http.rb +0 -1
  17. data/lib/httpx/pool.rb +0 -4
  18. data/lib/httpx/request/body.rb +22 -9
  19. data/lib/httpx/request.rb +63 -4
  20. data/lib/httpx/resolver/native.rb +2 -2
  21. data/lib/httpx/resolver/resolver.rb +5 -2
  22. data/lib/httpx/resolver/system.rb +5 -2
  23. data/lib/httpx/resolver.rb +6 -4
  24. data/lib/httpx/response/body.rb +30 -5
  25. data/lib/httpx/response/buffer.rb +20 -14
  26. data/lib/httpx/response.rb +95 -16
  27. data/lib/httpx/selector.rb +2 -2
  28. data/lib/httpx/session.rb +64 -2
  29. data/lib/httpx/timers.rb +35 -8
  30. data/lib/httpx/transcoder/json.rb +1 -1
  31. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  32. data/lib/httpx/version.rb +1 -1
  33. data/sig/connection/http1.rbs +3 -3
  34. data/sig/connection/http2.rbs +1 -1
  35. data/sig/connection.rbs +4 -1
  36. data/sig/io/tcp.rbs +1 -1
  37. data/sig/options.rbs +2 -2
  38. data/sig/pool.rbs +1 -1
  39. data/sig/request/body.rbs +0 -2
  40. data/sig/request.rbs +9 -3
  41. data/sig/resolver/native.rbs +1 -1
  42. data/sig/resolver.rbs +1 -1
  43. data/sig/response/body.rbs +0 -1
  44. data/sig/response.rbs +11 -3
  45. data/sig/timers.rbs +17 -7
  46. data/sig/transcoder/utils/inflater.rbs +12 -0
  47. metadata +8 -2
@@ -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
- attr_reader :threshold_size
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 cases
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
- attr_reader :verb, :uri, :headers, :body, :state, :options, :response
17
+ # the upcased string HTTP verb for this request.
18
+ attr_reader :verb
15
19
 
16
- # Exception raised during enumerable body writes
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
- # https://bugs.ruby-lang.org/issues/15278
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
- # https://bugs.ruby-lang.org/issues/15278
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[:ndots]
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 raise_timeout_error(interval)
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
- # double emission check
66
- emit_resolved_connection(connection, addresses) unless connection.addresses && addresses.intersect?(connection.addresses)
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 raise_timeout_error(interval)
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
- resolve_timeout = @timeouts[connection.origin.host].first
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
@@ -5,7 +5,7 @@ require "ipaddr"
5
5
 
6
6
  module HTTPX
7
7
  module Resolver
8
- RESOLVE_TIMEOUT = 5
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
- ips = @lookups[hostname].flat_map do |address|
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
 
@@ -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
- attr_reader :encoding, :encodings
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: @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
- try_upgrade_buffer
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
- if !@buffer.is_a?(Tempfile) && @bytesize > @threshold_size
61
- aux = @buffer
62
-
63
- @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
69
+ return unless @bytesize > @threshold_size
64
70
 
65
- if aux
66
- aux.rewind
67
- ::IO.copy_stream(aux, @buffer)
68
- aux.close
69
- end
71
+ return if @buffer.is_a?(Tempfile)
70
72
 
71
- else
72
- return if @buffer
73
+ aux = @buffer
73
74
 
74
- @buffer = StringIO.new("".b)
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