httpx 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0f330c17658b6d488b83008df23e73eb276c04fb93f6e72093224a1a6e1d839
4
- data.tar.gz: 75fea2fe69162c00d371ca83f121883d435be3d7d0749f1f2dce71332c6f18d2
3
+ metadata.gz: 7ef9f0a027f7a313547ca11bce6cf2c5a3eb72075b2cee0be13448d4aa7ec2b6
4
+ data.tar.gz: 0021a6c5e4968dd68cb23a157c5ce0dd84021bf134eb6c8227c5b145772e5812
5
5
  SHA512:
6
- metadata.gz: 4c3bf06316177777f8802fc7662021e565ddc19590a2cad092ab3e05759fa778e7edd4f9b6ee9359b582d8828f669236be517129af57a12d17cfe71cde7adf06
7
- data.tar.gz: 66ef58dbd0c3f4681bbde3e211afaaa7272d88aa4aa70e5791636147439a5bacb2bf85f89d0b33a792ddcf6444ae14e3c64685436b9cb849cb7fad30f3db1ef1
6
+ metadata.gz: 14687b4930aff7fef833058a27d5b26e4a9c327fcbf779f88d3b2b7766a861a26845285054ccde501a5a8296fdfc3c87a5f7e56eafc58fabeecd383512d36e8c
7
+ data.tar.gz: 95ee1b01958d2c680e194baa2f3136464c775038e9cd87d7d712d88998f48bbd385d6f3cc1173d2bda023102c4ca4661221aabd03d0d8a0a62e3006a79694851
data/README.md CHANGED
@@ -108,8 +108,8 @@ HTTPX.get(
108
108
  ```ruby
109
109
  response = HTTPX.get("https://www.google.com", params: { q: "me" })
110
110
  response = HTTPX.post("https://www.nghttp2.org/httpbin/post", form: {name: "John", age: "22"})
111
- response = HTTPX.plugin(:basic_authentication)
112
- .basic_authentication("user", "pass")
111
+ response = HTTPX.plugin(:basic_auth)
112
+ .basic_auth("user", "pass")
113
113
  .get("https://www.google.com")
114
114
 
115
115
  # more complex client objects can be cached, and are thread-safe
@@ -0,0 +1,32 @@
1
+ # 1.1.0
2
+
3
+ ## Features
4
+
5
+ A function, `#peer_address`, was added to the response object, which returns the IP (either a string or an `IPAddr` object) from the socket used to get the response from.
6
+
7
+ ```ruby
8
+ response = HTTPX.get("https://example.com")
9
+ response.peer_address #=> #<IPAddr: IPv4:93.184.216.34/255.255.255.255>
10
+ ```
11
+
12
+ error responses will also expose an IP address via `#peer_address` as long a connection happened before the error.
13
+
14
+ ## Improvements
15
+
16
+ * A performance regression involving the new default timeouts has been fixed, which could cause significant overhead in "multiple requests in sequence" scenarios, and was clearly visible in benchmarks.
17
+ * this regression will still be seen in jruby due to a bug, which fix will be released in jruby 9.4.5.0.
18
+ * HTTP/1.1 connections are now set to handle as many requests as they can by default (instead of the past default of max 200, at which point they'd be recycled).
19
+ * tolerate the inexistence of `openssl` in the installed ruby, like `net-http` does.
20
+ * `on_connection_opened` and `on_connection_closed` will yield the `OpenSSL::SSL::SSLSocket` instance for `https` backed origins (instead of always the `Socket` instance).
21
+
22
+ ## Bugfixes
23
+
24
+ * when using the `:native` resolver (default option), a default of 1 for ndots is set, for systems which do not set one.
25
+ * replaced usage of `Float::INFINITY` with `nil` for timeout defaults, as the former can't be used in IO wait functions.
26
+ * `faraday` adapter timeout setup now maps to `:read_timeout` and `:write_timeout` options from `httpx`.
27
+ * fixed HTTP/1.1 connection recycling on number of max requests exhausted.
28
+ * `response.json` will now work when "content-type" header is set to "application/hal+json".
29
+
30
+ ## Chore
31
+
32
+ * when using the `:cookies` plugin, a warning message to install the idnx message will only be emitted if the cookie domain is an IDN (this message was being shown all the time since v1 release).
@@ -69,38 +69,47 @@ module Faraday
69
69
  timeout_options = {}
70
70
  req_opts = env.request
71
71
  if (sec = request_timeout(:read, req_opts))
72
- timeout_options[:operation_timeout] = sec
72
+ timeout_options[:read_timeout] = sec
73
73
  end
74
74
 
75
75
  if (sec = request_timeout(:write, req_opts))
76
- timeout_options[:operation_timeout] = sec
76
+ timeout_options[:write_timeout] = sec
77
77
  end
78
78
 
79
79
  if (sec = request_timeout(:open, req_opts))
80
80
  timeout_options[:connect_timeout] = sec
81
81
  end
82
82
 
83
- ssl_options = {}
84
-
85
- unless env.ssl.verify.nil?
86
- ssl_options[:verify_mode] = env.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
87
- end
88
-
89
- ssl_options[:ca_file] = env.ssl.ca_file if env.ssl.ca_file
90
- ssl_options[:ca_path] = env.ssl.ca_path if env.ssl.ca_path
91
- ssl_options[:cert_store] = env.ssl.cert_store if env.ssl.cert_store
92
- ssl_options[:cert] = env.ssl.client_cert if env.ssl.client_cert
93
- ssl_options[:key] = env.ssl.client_key if env.ssl.client_key
94
- ssl_options[:ssl_version] = env.ssl.version if env.ssl.version
95
- ssl_options[:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
96
- ssl_options[:min_version] = env.ssl.min_version if env.ssl.min_version
97
- ssl_options[:max_version] = env.ssl.max_version if env.ssl.max_version
98
-
99
83
  {
100
- ssl: ssl_options,
84
+ ssl: ssl_options_from_env(env),
101
85
  timeout: timeout_options,
102
86
  }
103
87
  end
88
+
89
+ if defined?(::OpenSSL)
90
+ def ssl_options_from_env(env)
91
+ ssl_options = {}
92
+
93
+ unless env.ssl.verify.nil?
94
+ ssl_options[:verify_mode] = env.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
95
+ end
96
+
97
+ ssl_options[:ca_file] = env.ssl.ca_file if env.ssl.ca_file
98
+ ssl_options[:ca_path] = env.ssl.ca_path if env.ssl.ca_path
99
+ ssl_options[:cert_store] = env.ssl.cert_store if env.ssl.cert_store
100
+ ssl_options[:cert] = env.ssl.client_cert if env.ssl.client_cert
101
+ ssl_options[:key] = env.ssl.client_key if env.ssl.client_key
102
+ ssl_options[:ssl_version] = env.ssl.version if env.ssl.version
103
+ ssl_options[:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
104
+ ssl_options[:min_version] = env.ssl.min_version if env.ssl.min_version
105
+ ssl_options[:max_version] = env.ssl.max_version if env.ssl.max_version
106
+ ssl_options
107
+ end
108
+ else
109
+ def ssl_options_from_env(*)
110
+ {}
111
+ end
112
+ end
104
113
  end
105
114
 
106
115
  include RequestMixin
@@ -15,7 +15,7 @@ module HTTPX
15
15
  def initialize(buffer, options)
16
16
  @options = Options.new(options)
17
17
  @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
18
- @max_requests = @options.max_requests || MAX_REQUESTS
18
+ @max_requests = @options.max_requests
19
19
  @parser = Parser::HTTP1.new(self)
20
20
  @buffer = buffer
21
21
  @version = [1, 1]
@@ -184,7 +184,14 @@ module HTTPX
184
184
  manage_connection(response)
185
185
  end
186
186
 
187
- send(@pending.shift) unless @pending.empty?
187
+ if exhausted?
188
+ @pending.concat(@requests)
189
+ @requests.clear
190
+
191
+ emit(:exhausted)
192
+ else
193
+ send(@pending.shift) unless @pending.empty?
194
+ end
188
195
  end
189
196
 
190
197
  def handle_error(ex)
@@ -287,7 +294,7 @@ module HTTPX
287
294
 
288
295
  connection = request.headers["connection"]
289
296
 
290
- connection ||= if request.options.persistent
297
+ connection ||= if request.persistent?
291
298
  # when in a persistent connection, the request can't be at
292
299
  # the edge of a renegotiation
293
300
  if @requests.index(request) + 1 < @max_requests
@@ -35,7 +35,7 @@ module HTTPX
35
35
  @handshake_completed = false
36
36
  @wait_for_handshake = @settings.key?(:wait_for_handshake) ? @settings.delete(:wait_for_handshake) : true
37
37
  @max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
38
- @max_requests = @options.max_requests || Float::INFINITY
38
+ @max_requests = @options.max_requests
39
39
  init_connection
40
40
  end
41
41
 
@@ -70,6 +70,8 @@ module HTTPX
70
70
  @inflight = 0
71
71
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
72
72
 
73
+ @intervals = []
74
+
73
75
  self.addresses = @options.addresses if @options.addresses
74
76
  end
75
77
 
@@ -213,6 +215,9 @@ module HTTPX
213
215
 
214
216
  def call
215
217
  case @state
218
+ when :idle
219
+ connect
220
+ consume
216
221
  when :closed
217
222
  return
218
223
  when :closing
@@ -294,7 +299,15 @@ module HTTPX
294
299
  @state == :open || @state == :inactive
295
300
  end
296
301
 
297
- def raise_timeout_error(interval)
302
+ def handle_socket_timeout(interval)
303
+ @intervals.delete_if(&:elapsed?)
304
+
305
+ unless @intervals.empty?
306
+ # remove the intervals which will elapse
307
+
308
+ return
309
+ end
310
+
298
311
  error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
299
312
  error.set_backtrace(caller)
300
313
  on_error(error)
@@ -449,6 +462,7 @@ module HTTPX
449
462
 
450
463
  def send_request_to_parser(request)
451
464
  @inflight += 1
465
+ request.peer_address = @io.ip
452
466
  parser.send(request)
453
467
 
454
468
  set_request_timeouts(request)
@@ -655,19 +669,26 @@ module HTTPX
655
669
 
656
670
  def set_request_timeouts(request)
657
671
  write_timeout = request.write_timeout
658
- request.once(:headers) do
659
- @timers.after(write_timeout) { write_timeout_callback(request, write_timeout) }
660
- end unless write_timeout.nil? || write_timeout.infinite?
661
-
662
672
  read_timeout = request.read_timeout
663
- request.once(:done) do
664
- @timers.after(read_timeout) { read_timeout_callback(request, read_timeout) }
665
- end unless read_timeout.nil? || read_timeout.infinite?
666
-
667
673
  request_timeout = request.request_timeout
668
- request.once(:headers) do
669
- @timers.after(request_timeout) { read_timeout_callback(request, request_timeout, RequestTimeoutError) }
670
- end unless request_timeout.nil? || request_timeout.infinite?
674
+
675
+ unless write_timeout.nil? || write_timeout.infinite?
676
+ set_request_timeout(request, write_timeout, :headers, %i[done response]) do
677
+ write_timeout_callback(request, write_timeout)
678
+ end
679
+ end
680
+
681
+ unless read_timeout.nil? || read_timeout.infinite?
682
+ set_request_timeout(request, read_timeout, :done, :response) do
683
+ read_timeout_callback(request, read_timeout)
684
+ end
685
+ end
686
+
687
+ return if request_timeout.nil? || request_timeout.infinite?
688
+
689
+ set_request_timeout(request, request_timeout, :headers, :response) do
690
+ read_timeout_callback(request, request_timeout, RequestTimeoutError)
691
+ end
671
692
  end
672
693
 
673
694
  def write_timeout_callback(request, write_timeout)
@@ -688,6 +709,24 @@ module HTTPX
688
709
  on_error(error)
689
710
  end
690
711
 
712
+ def set_request_timeout(request, timeout, start_event, finish_events, &callback)
713
+ request.once(start_event) do
714
+ interval = @timers.after(timeout, callback)
715
+
716
+ Array(finish_events).each do |event|
717
+ # clean up reques timeouts if the connection errors out
718
+ request.once(event) do
719
+ if @intervals.include?(interval)
720
+ interval.delete(callback)
721
+ @intervals.delete(interval) if interval.no_callbacks?
722
+ end
723
+ end
724
+ end
725
+
726
+ @intervals << interval
727
+ end
728
+ end
729
+
691
730
  class << self
692
731
  def parser_type(protocol)
693
732
  case protocol
@@ -61,8 +61,12 @@ module HTTPX
61
61
  # Normalizes a _domain_ using the Punycode algorithm as necessary.
62
62
  # The result will be a downcased, ASCII-only string.
63
63
  def normalize(domain)
64
- domain = domain.chomp(".").unicode_normalize(:nfc) unless domain.ascii_only?
65
- Punycode.encode_hostname(domain).downcase
64
+ unless domain.ascii_only?
65
+ domain = domain.chomp(".").unicode_normalize(:nfc)
66
+ domain = Punycode.encode_hostname(domain)
67
+ end
68
+
69
+ domain.downcase
66
70
  end
67
71
  end
68
72
 
data/lib/httpx/errors.rb CHANGED
@@ -1,20 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
+ # the default exception class for exceptions raised by HTTPX.
4
5
  class Error < StandardError; end
5
6
 
6
7
  class UnsupportedSchemeError < Error; end
7
8
 
8
9
  class ConnectionError < Error; end
9
10
 
11
+ # Error raised when there was a timeout. Its subclasses allow for finer-grained
12
+ # control of which timeout happened.
10
13
  class TimeoutError < Error
14
+ # The timeout value which caused this error to be raised.
11
15
  attr_reader :timeout
12
16
 
17
+ # initializes the timeout exception with the +timeout+ causing the error, and the
18
+ # error +message+ for it.
13
19
  def initialize(timeout, message)
14
20
  @timeout = timeout
15
21
  super(message)
16
22
  end
17
23
 
24
+ # clones this error into a HTTPX::ConnectionTimeoutError.
18
25
  def to_connection_error
19
26
  ex = ConnectTimeoutError.new(@timeout, message)
20
27
  ex.set_backtrace(backtrace)
@@ -22,11 +29,19 @@ module HTTPX
22
29
  end
23
30
  end
24
31
 
32
+ # Error raised when there was a timeout establishing the connection to a server.
33
+ # This may be raised due to timeouts during TCP and TLS (when applicable) connection
34
+ # establishment.
25
35
  class ConnectTimeoutError < TimeoutError; end
26
36
 
37
+ # Error raised when there was a timeout while sending a request, or receiving a response
38
+ # from the server.
27
39
  class RequestTimeoutError < TimeoutError
40
+ # The HTTPX::Request request object this exception refers to.
28
41
  attr_reader :request
29
42
 
43
+ # initializes the exception with the +request+ and +response+ it refers to, and the
44
+ # +timeout+ causing the error, and the
30
45
  def initialize(request, response, timeout)
31
46
  @request = request
32
47
  @response = response
@@ -38,19 +53,28 @@ module HTTPX
38
53
  end
39
54
  end
40
55
 
56
+ # Error raised when there was a timeout while receiving a response from the server.
41
57
  class ReadTimeoutError < RequestTimeoutError; end
42
58
 
59
+ # Error raised when there was a timeout while sending a request from the server.
43
60
  class WriteTimeoutError < RequestTimeoutError; end
44
61
 
62
+ # Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
45
63
  class SettingsTimeoutError < TimeoutError; end
46
64
 
65
+ # Error raised when there was a timeout while resolving a domain to an IP.
47
66
  class ResolveTimeoutError < TimeoutError; end
48
67
 
68
+ # Error raised when there was an error while resolving a domain to an IP.
49
69
  class ResolveError < Error; end
50
70
 
71
+ # Error raised when there was an error while resolving a domain to an IP
72
+ # using a HTTPX::Resolver::Native resolver.
51
73
  class NativeResolveError < ResolveError
52
74
  attr_reader :connection, :host
53
75
 
76
+ # initializes the exception with the +connection+ it refers to, the +host+ domain
77
+ # which failed to resolve, and the error +message+.
54
78
  def initialize(connection, host, message = "Can't resolve #{host}")
55
79
  @connection = connection
56
80
  @host = host
@@ -58,18 +82,26 @@ module HTTPX
58
82
  end
59
83
  end
60
84
 
85
+ # The exception class for HTTP responses with 4xx or 5xx status.
61
86
  class HTTPError < Error
87
+ # The HTTPX::Response response object this exception refers to.
62
88
  attr_reader :response
63
89
 
90
+ # Creates the instance and assigns the HTTPX::Response +response+.
64
91
  def initialize(response)
65
92
  @response = response
66
93
  super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
67
94
  end
68
95
 
96
+ # The HTTP response status.
97
+ #
98
+ # error.status #=> 404
69
99
  def status
70
100
  @response.status
71
101
  end
72
102
  end
73
103
 
104
+ # error raised when a request was sent a server which can't reproduce a response, and
105
+ # has therefore returned an HTTP response using the 421 status code.
74
106
  class MisdirectedRequestError < HTTPError; end
75
107
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -114,7 +114,9 @@ module HTTPX
114
114
  end
115
115
 
116
116
  def try_ssl_connect
117
- case @io.connect_nonblock(exception: false)
117
+ ret = @io.connect_nonblock(exception: false)
118
+ log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
119
+ case ret
118
120
  when :wait_readable
119
121
  @interests = :r
120
122
  return
data/lib/httpx/io/tcp.rb CHANGED
@@ -41,7 +41,7 @@ module HTTPX
41
41
  end
42
42
 
43
43
  def socket
44
- @io.to_io
44
+ @io
45
45
  end
46
46
 
47
47
  def add_addresses(addrs)
@@ -95,7 +95,9 @@ module HTTPX
95
95
  end
96
96
 
97
97
  def try_connect
98
- case @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false)
98
+ ret = @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false)
99
+ log(level: 3, color: :cyan) { "TCP CONNECT: #{ret}..." }
100
+ case ret
99
101
  when :wait_readable
100
102
  @interests = :r
101
103
  return
data/lib/httpx/io.rb CHANGED
@@ -4,4 +4,8 @@ require "socket"
4
4
  require "httpx/io/udp"
5
5
  require "httpx/io/tcp"
6
6
  require "httpx/io/unix"
7
- require "httpx/io/ssl"
7
+
8
+ begin
9
+ require "httpx/io/ssl"
10
+ rescue LoadError
11
+ end
data/lib/httpx/options.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require "socket"
4
4
 
5
5
  module HTTPX
6
+ # Contains a set of options which are passed and shared across from session to its requests or
7
+ # responses.
6
8
  class Options
7
9
  BUFFER_SIZE = 1 << 14
8
10
  WINDOW_SIZE = 1 << 14 # 16K
@@ -10,7 +12,7 @@ module HTTPX
10
12
  KEEP_ALIVE_TIMEOUT = 20
11
13
  SETTINGS_TIMEOUT = 10
12
14
  CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
13
- REQUEST_TIMEOUT = OPERATION_TIMEOUT = Float::INFINITY
15
+ REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
14
16
 
15
17
  # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
16
18
  ip_address_families = begin
@@ -25,6 +27,7 @@ module HTTPX
25
27
  end
26
28
 
27
29
  DEFAULT_OPTIONS = {
30
+ :max_requests => Float::INFINITY,
28
31
  :debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
29
32
  :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
30
33
  :ssl => {},
@@ -81,6 +84,50 @@ module HTTPX
81
84
  end
82
85
  end
83
86
 
87
+ # creates a new options instance from a given hash, which optionally define the following:
88
+ #
89
+ # :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
90
+ # :debug_level :: the log level of messages (can be 1, 2, or 3).
91
+ # :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
92
+ # :http2_settings :: a hash of options to be passed to a HTTP2Next::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
93
+ # :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
94
+ # like ALPN (defaults to <tt>"http/1.1"</tt>)
95
+ # :supported_compression_formats :: list of compressions supported by the transcoder layer (defaults to <tt>%w[gzip deflate]</tt>).
96
+ # :decompress_response_body :: whether to auto-decompress response body (defaults to <tt>true</tt>).
97
+ # :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
98
+ # :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
99
+ # <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>
100
+ # and <tt>:request_timeout</tt>
101
+ # :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
102
+ # :window_size :: number of bytes to read from a socket
103
+ # :buffer_size :: internal read and write buffer size in bytes
104
+ # :body_threshold_size :: maximum size in bytes of response payload that is buffered in memory.
105
+ # :request_class :: class used to instantiate a request
106
+ # :response_class :: class used to instantiate a response
107
+ # :headers_class :: class used to instantiate headers
108
+ # :request_body_class :: class used to instantiate a request body
109
+ # :response_body_class :: class used to instantiate a response body
110
+ # :connection_class :: class used to instantiate connections
111
+ # :options_class :: class used to instantiate options
112
+ # :transport :: type of transport to use (set to "unix" for UNIX sockets)
113
+ # :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
114
+ # paths should be used for UNIX sockets instead)
115
+ # :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
116
+ # :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
117
+ # :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
118
+ # using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
119
+ # :resolver_options :: hash of options passed to the resolver
120
+ # :ip_families :: which socket families are supported (system-dependent)
121
+ # :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
122
+ # :base_path :: path to prefix given relative paths with (ex: "/v2")
123
+ # :max_concurrent_requests :: max number of requests which can be set concurrently
124
+ # :max_requests :: max number of requests which can be made on socket before it reconnects.
125
+ # :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
126
+ # :form :: hash of array of key-values which will be form-or-multipart-encoded in requests body payload.
127
+ # :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
128
+ # :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
129
+ #
130
+ # This list of options are enhanced with each loaded plugin, see the plugin docs for details.
84
131
  def initialize(options = {})
85
132
  do_initialize(options)
86
133
  freeze
@@ -75,14 +75,16 @@ module HTTPX
75
75
 
76
76
  return unless request.headers["expect"] == "100-continue"
77
77
 
78
- request.once(:expect) do
79
- @timers.after(request.options.expect_timeout) do
80
- # expect timeout expired
81
- if request.state == :expect && !request.expects?
82
- Expect.no_expect_store << request.origin
83
- request.headers.delete("expect")
84
- consume
85
- end
78
+ expect_timeout = request.options.expect_timeout
79
+
80
+ return if expect_timeout.nil? || expect_timeout.infinite?
81
+
82
+ set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
83
+ # expect timeout expired
84
+ if request.state == :expect && !request.expects?
85
+ Expect.no_expect_store << request.origin
86
+ request.headers.delete("expect")
87
+ consume
86
88
  end
87
89
  end
88
90
  end
data/lib/httpx/pool.rb CHANGED
@@ -52,7 +52,6 @@ module HTTPX
52
52
  def close(connections = @connections)
53
53
  return if connections.empty?
54
54
 
55
- @timers.cancel
56
55
  @eden_connections.clear
57
56
  connections = connections.reject(&:inflight?)
58
57
  connections.each(&:close)
@@ -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