httpx 0.21.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +54 -45
  4. data/doc/release_notes/0_10_0.md +2 -2
  5. data/doc/release_notes/0_11_0.md +3 -5
  6. data/doc/release_notes/0_12_0.md +5 -5
  7. data/doc/release_notes/0_13_0.md +4 -4
  8. data/doc/release_notes/0_14_0.md +2 -2
  9. data/doc/release_notes/0_16_0.md +3 -3
  10. data/doc/release_notes/0_17_0.md +1 -1
  11. data/doc/release_notes/0_18_0.md +4 -4
  12. data/doc/release_notes/0_18_2.md +1 -1
  13. data/doc/release_notes/0_19_0.md +1 -1
  14. data/doc/release_notes/0_20_0.md +1 -1
  15. data/doc/release_notes/0_21_0.md +7 -5
  16. data/doc/release_notes/0_21_1.md +12 -0
  17. data/doc/release_notes/0_22_0.md +13 -0
  18. data/doc/release_notes/0_22_1.md +11 -0
  19. data/doc/release_notes/0_22_2.md +5 -0
  20. data/doc/release_notes/0_22_3.md +55 -0
  21. data/doc/release_notes/0_22_4.md +6 -0
  22. data/doc/release_notes/0_22_5.md +6 -0
  23. data/doc/release_notes/0_23_0.md +42 -0
  24. data/doc/release_notes/0_23_1.md +5 -0
  25. data/doc/release_notes/0_23_2.md +5 -0
  26. data/doc/release_notes/0_23_3.md +6 -0
  27. data/doc/release_notes/0_23_4.md +5 -0
  28. data/doc/release_notes/0_24_0.md +48 -0
  29. data/doc/release_notes/0_24_1.md +12 -0
  30. data/doc/release_notes/0_24_2.md +12 -0
  31. data/doc/release_notes/0_24_3.md +12 -0
  32. data/doc/release_notes/0_24_4.md +18 -0
  33. data/doc/release_notes/0_24_5.md +6 -0
  34. data/doc/release_notes/0_24_6.md +5 -0
  35. data/doc/release_notes/0_24_7.md +10 -0
  36. data/doc/release_notes/1_0_0.md +60 -0
  37. data/doc/release_notes/1_0_1.md +5 -0
  38. data/doc/release_notes/1_0_2.md +7 -0
  39. data/doc/release_notes/1_1_0.md +32 -0
  40. data/doc/release_notes/1_1_1.md +17 -0
  41. data/doc/release_notes/1_1_2.md +12 -0
  42. data/doc/release_notes/1_1_3.md +18 -0
  43. data/doc/release_notes/1_1_4.md +6 -0
  44. data/doc/release_notes/1_1_5.md +12 -0
  45. data/doc/release_notes/1_2_0.md +49 -0
  46. data/doc/release_notes/1_2_1.md +6 -0
  47. data/lib/httpx/adapters/datadog.rb +100 -106
  48. data/lib/httpx/adapters/faraday.rb +143 -107
  49. data/lib/httpx/adapters/sentry.rb +26 -7
  50. data/lib/httpx/adapters/webmock.rb +33 -17
  51. data/lib/httpx/altsvc.rb +61 -24
  52. data/lib/httpx/base64.rb +27 -0
  53. data/lib/httpx/buffer.rb +12 -0
  54. data/lib/httpx/callbacks.rb +5 -3
  55. data/lib/httpx/chainable.rb +54 -39
  56. data/lib/httpx/connection/http1.rb +62 -37
  57. data/lib/httpx/connection/http2.rb +16 -27
  58. data/lib/httpx/connection.rb +213 -120
  59. data/lib/httpx/domain_name.rb +10 -13
  60. data/lib/httpx/errors.rb +34 -2
  61. data/lib/httpx/extensions.rb +4 -134
  62. data/lib/httpx/io/ssl.rb +77 -71
  63. data/lib/httpx/io/tcp.rb +46 -70
  64. data/lib/httpx/io/udp.rb +18 -52
  65. data/lib/httpx/io/unix.rb +6 -13
  66. data/lib/httpx/io.rb +3 -9
  67. data/lib/httpx/loggable.rb +4 -19
  68. data/lib/httpx/options.rb +168 -110
  69. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  70. data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
  71. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  72. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  73. data/lib/httpx/plugins/auth.rb +25 -0
  74. data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
  75. data/lib/httpx/plugins/aws_sigv4.rb +5 -6
  76. data/lib/httpx/plugins/basic_auth.rb +29 -0
  77. data/lib/httpx/plugins/brotli.rb +50 -0
  78. data/lib/httpx/plugins/callbacks.rb +91 -0
  79. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  80. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
  81. data/lib/httpx/plugins/circuit_breaker.rb +30 -7
  82. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  83. data/lib/httpx/plugins/cookies.rb +20 -10
  84. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
  85. data/lib/httpx/plugins/expect.rb +15 -13
  86. data/lib/httpx/plugins/follow_redirects.rb +71 -29
  87. data/lib/httpx/plugins/grpc/call.rb +2 -3
  88. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  89. data/lib/httpx/plugins/grpc/message.rb +7 -37
  90. data/lib/httpx/plugins/grpc.rb +35 -29
  91. data/lib/httpx/plugins/h2c.rb +25 -18
  92. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  93. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  94. data/lib/httpx/plugins/oauth.rb +170 -0
  95. data/lib/httpx/plugins/persistent.rb +1 -1
  96. data/lib/httpx/plugins/proxy/http.rb +15 -10
  97. data/lib/httpx/plugins/proxy/socks4.rb +8 -6
  98. data/lib/httpx/plugins/proxy/socks5.rb +10 -8
  99. data/lib/httpx/plugins/proxy.rb +69 -67
  100. data/lib/httpx/plugins/push_promise.rb +1 -1
  101. data/lib/httpx/plugins/rate_limiter.rb +3 -1
  102. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  103. data/lib/httpx/plugins/response_cache/store.rb +34 -17
  104. data/lib/httpx/plugins/response_cache.rb +6 -6
  105. data/lib/httpx/plugins/retries.rb +61 -12
  106. data/lib/httpx/plugins/ssrf_filter.rb +142 -0
  107. data/lib/httpx/plugins/stream.rb +27 -32
  108. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  109. data/lib/httpx/plugins/upgrade.rb +8 -10
  110. data/lib/httpx/plugins/webdav.rb +10 -8
  111. data/lib/httpx/pool.rb +85 -23
  112. data/lib/httpx/punycode.rb +9 -291
  113. data/lib/httpx/request/body.rb +158 -0
  114. data/lib/httpx/request.rb +86 -121
  115. data/lib/httpx/resolver/https.rb +54 -17
  116. data/lib/httpx/resolver/multi.rb +8 -12
  117. data/lib/httpx/resolver/native.rb +163 -70
  118. data/lib/httpx/resolver/resolver.rb +28 -13
  119. data/lib/httpx/resolver/system.rb +15 -10
  120. data/lib/httpx/resolver.rb +38 -16
  121. data/lib/httpx/response/body.rb +242 -0
  122. data/lib/httpx/response/buffer.rb +96 -0
  123. data/lib/httpx/response.rb +113 -211
  124. data/lib/httpx/selector.rb +2 -4
  125. data/lib/httpx/session.rb +91 -64
  126. data/lib/httpx/session_extensions.rb +4 -1
  127. data/lib/httpx/timers.rb +28 -8
  128. data/lib/httpx/transcoder/body.rb +0 -2
  129. data/lib/httpx/transcoder/chunker.rb +0 -1
  130. data/lib/httpx/transcoder/deflate.rb +37 -0
  131. data/lib/httpx/transcoder/form.rb +52 -33
  132. data/lib/httpx/transcoder/gzip.rb +74 -0
  133. data/lib/httpx/transcoder/json.rb +2 -5
  134. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  135. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  136. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  137. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  138. data/lib/httpx/transcoder/multipart.rb +17 -0
  139. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  140. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  141. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  142. data/lib/httpx/transcoder/xml.rb +0 -5
  143. data/lib/httpx/transcoder.rb +4 -6
  144. data/lib/httpx/utils.rb +36 -16
  145. data/lib/httpx/version.rb +1 -1
  146. data/lib/httpx.rb +12 -14
  147. data/sig/altsvc.rbs +33 -0
  148. data/sig/buffer.rbs +1 -0
  149. data/sig/callbacks.rbs +3 -3
  150. data/sig/chainable.rbs +10 -9
  151. data/sig/connection/http1.rbs +5 -4
  152. data/sig/connection/http2.rbs +1 -1
  153. data/sig/connection.rbs +46 -24
  154. data/sig/errors.rbs +9 -3
  155. data/sig/httpx.rbs +5 -4
  156. data/sig/io/ssl.rbs +26 -0
  157. data/sig/io/tcp.rbs +60 -0
  158. data/sig/io/udp.rbs +20 -0
  159. data/sig/io/unix.rbs +10 -0
  160. data/sig/options.rbs +28 -12
  161. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  162. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  163. data/sig/plugins/auth.rbs +13 -0
  164. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  165. data/sig/plugins/brotli.rbs +22 -0
  166. data/sig/plugins/callbacks.rbs +38 -0
  167. data/sig/plugins/circuit_breaker.rbs +13 -3
  168. data/sig/plugins/compression.rbs +6 -4
  169. data/sig/plugins/cookies/jar.rbs +2 -2
  170. data/sig/plugins/cookies.rbs +2 -0
  171. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  172. data/sig/plugins/follow_redirects.rbs +11 -2
  173. data/sig/plugins/grpc/call.rbs +19 -0
  174. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  175. data/sig/plugins/grpc/message.rbs +17 -0
  176. data/sig/plugins/grpc.rbs +2 -32
  177. data/sig/plugins/h2c.rbs +1 -1
  178. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  179. data/sig/plugins/oauth.rbs +54 -0
  180. data/sig/plugins/proxy/socks4.rbs +4 -4
  181. data/sig/plugins/proxy/socks5.rbs +2 -2
  182. data/sig/plugins/proxy/ssh.rbs +1 -1
  183. data/sig/plugins/proxy.rbs +10 -4
  184. data/sig/plugins/response_cache.rbs +12 -3
  185. data/sig/plugins/retries.rbs +28 -8
  186. data/sig/plugins/stream.rbs +24 -17
  187. data/sig/plugins/upgrade.rbs +5 -3
  188. data/sig/pool.rbs +5 -4
  189. data/sig/request/body.rbs +40 -0
  190. data/sig/request.rbs +12 -28
  191. data/sig/resolver/https.rbs +7 -2
  192. data/sig/resolver/native.rbs +10 -4
  193. data/sig/resolver/resolver.rbs +6 -4
  194. data/sig/resolver/system.rbs +2 -0
  195. data/sig/resolver.rbs +9 -5
  196. data/sig/response/body.rbs +53 -0
  197. data/sig/response/buffer.rbs +24 -0
  198. data/sig/response.rbs +17 -38
  199. data/sig/session.rbs +24 -18
  200. data/sig/timers.rbs +17 -7
  201. data/sig/transcoder/body.rbs +4 -3
  202. data/sig/transcoder/deflate.rbs +11 -0
  203. data/sig/transcoder/form.rbs +5 -3
  204. data/sig/transcoder/gzip.rbs +24 -0
  205. data/sig/transcoder/json.rbs +4 -2
  206. data/sig/{plugins → transcoder}/multipart.rbs +3 -12
  207. data/sig/transcoder/utils/body_reader.rbs +15 -0
  208. data/sig/transcoder/utils/deflater.rbs +29 -0
  209. data/sig/transcoder/utils/inflater.rbs +12 -0
  210. data/sig/transcoder/xml.rbs +1 -1
  211. data/sig/transcoder.rbs +22 -7
  212. data/sig/utils.rbs +2 -0
  213. metadata +127 -40
  214. data/lib/httpx/plugins/authentication.rb +0 -20
  215. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  216. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  217. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  218. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  219. data/lib/httpx/plugins/compression.rb +0 -164
  220. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  221. data/lib/httpx/plugins/multipart.rb +0 -84
  222. data/lib/httpx/registry.rb +0 -85
  223. data/sig/plugins/authentication.rbs +0 -11
  224. data/sig/plugins/compression/brotli.rbs +0 -21
  225. data/sig/plugins/compression/deflate.rbs +0 -17
  226. data/sig/plugins/compression/gzip.rbs +0 -29
  227. data/sig/registry.rbs +0 -13
  228. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  229. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/io/udp.rb CHANGED
@@ -6,11 +6,10 @@ module HTTPX
6
6
  class UDP
7
7
  include Loggable
8
8
 
9
- def initialize(uri, _, options)
10
- ip = IPAddr.new(uri.host)
11
- @host = ip.to_s
12
- @port = uri.port
13
- @io = UDPSocket.new(ip.family)
9
+ def initialize(ip, port, options)
10
+ @host = ip
11
+ @port = port
12
+ @io = UDPSocket.new(IPAddr.new(ip).family)
14
13
  @options = options
15
14
  end
16
15
 
@@ -24,45 +23,19 @@ module HTTPX
24
23
  true
25
24
  end
26
25
 
27
- if RUBY_VERSION < "2.3"
28
- # :nocov:
29
- def close
30
- @io.close
31
- rescue StandardError
32
- nil
33
- end
34
- # :nocov:
35
- else
36
- def close
37
- @io.close
38
- end
26
+ def close
27
+ @io.close
39
28
  end
40
29
 
41
- # :nocov:
42
- if (RUBY_ENGINE == "truffleruby" && RUBY_ENGINE_VERSION < "21.1.0") ||
43
- RUBY_VERSION < "2.3"
30
+ if RUBY_ENGINE == "jruby"
31
+ # In JRuby, sendmsg_nonblock is not implemented
44
32
  def write(buffer)
45
- siz = @io.sendmsg_nonblock(buffer.to_s, 0, Socket.sockaddr_in(@port, @host.to_s))
33
+ siz = @io.send(buffer.to_s, 0, @host, @port)
46
34
  log { "WRITE: #{siz} bytes..." }
47
35
  buffer.shift!(siz)
48
36
  siz
49
- rescue ::IO::WaitWritable
50
- 0
51
- rescue EOFError
52
- nil
53
- end
54
-
55
- def read(size, buffer)
56
- data, _ = @io.recvfrom_nonblock(size)
57
- buffer.replace(data)
58
- log { "READ: #{buffer.bytesize} bytes..." }
59
- buffer.bytesize
60
- rescue ::IO::WaitReadable
61
- 0
62
- rescue IOError
63
37
  end
64
38
  else
65
-
66
39
  def write(buffer)
67
40
  siz = @io.sendmsg_nonblock(buffer.to_s, 0, Socket.sockaddr_in(@port, @host.to_s), exception: false)
68
41
  return 0 if siz == :wait_writable
@@ -73,24 +46,17 @@ module HTTPX
73
46
  buffer.shift!(siz)
74
47
  siz
75
48
  end
49
+ end
76
50
 
77
- def read(size, buffer)
78
- ret = @io.recvfrom_nonblock(size, 0, buffer, exception: false)
79
- return 0 if ret == :wait_readable
80
- return if ret.nil?
51
+ def read(size, buffer)
52
+ ret = @io.recvfrom_nonblock(size, 0, buffer, exception: false)
53
+ return 0 if ret == :wait_readable
54
+ return if ret.nil?
81
55
 
82
- buffer.bytesize
83
- rescue IOError
84
- end
85
- end
56
+ log { "READ: #{buffer.bytesize} bytes..." }
86
57
 
87
- # In JRuby, sendmsg_nonblock is not implemented
88
- def write(buffer)
89
- siz = @io.send(buffer.to_s, 0, @host, @port)
90
- log { "WRITE: #{siz} bytes..." }
91
- buffer.shift!(siz)
92
- siz
93
- end if RUBY_ENGINE == "jruby"
94
- # :nocov:
58
+ buffer.bytesize
59
+ rescue IOError
60
+ end
95
61
  end
96
62
  end
data/lib/httpx/io/unix.rb CHANGED
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
-
5
3
  module HTTPX
6
4
  class UNIX < TCP
7
- extend Forwardable
8
-
9
5
  using URIExtensions
10
6
 
11
7
  attr_reader :path
@@ -13,7 +9,7 @@ module HTTPX
13
9
  alias_method :host, :path
14
10
 
15
11
  def initialize(origin, addresses, options)
16
- @addresses = addresses
12
+ @addresses = []
17
13
  @hostname = origin.host
18
14
  @state = :idle
19
15
  @options = Options.new(options)
@@ -31,14 +27,7 @@ module HTTPX
31
27
  @keep_open = true
32
28
  @state = :connected
33
29
  else
34
- if @options.transport_options
35
- # :nocov:
36
- warn ":transport_options is deprecated, use :addresses instead"
37
- @path = @options.transport_options[:path]
38
- # :nocov:
39
- else
40
- @path = addresses.first
41
- end
30
+ @path = addresses.first
42
31
  end
43
32
  @io ||= build_socket
44
33
  end
@@ -60,6 +49,10 @@ module HTTPX
60
49
  ::IO::WaitReadable
61
50
  end
62
51
 
52
+ def expired?
53
+ false
54
+ end
55
+
63
56
  # :nocov:
64
57
  def inspect
65
58
  "#<#{self.class}(path: #{@path}): (state: #{@state})>"
data/lib/httpx/io.rb CHANGED
@@ -4,14 +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"
8
7
 
9
- module HTTPX
10
- module IO
11
- extend Registry
12
- register "udp", UDP
13
- register "unix", HTTPX::UNIX
14
- register "tcp", TCP
15
- register "ssl", SSL
16
- end
8
+ begin
9
+ require "httpx/io/ssl"
10
+ rescue LoadError
17
11
  end
@@ -24,26 +24,11 @@ module HTTPX
24
24
  debug_stream << message
25
25
  end
26
26
 
27
- if Exception.instance_methods.include?(:full_message)
28
-
29
- def log_exception(ex, level: @options.debug_level, color: nil)
30
- return unless @options.debug
31
- return unless @options.debug_level >= level
32
-
33
- log(level: level, color: color) { ex.full_message }
34
- end
35
-
36
- else
37
-
38
- def log_exception(ex, level: @options.debug_level, color: nil)
39
- return unless @options.debug
40
- return unless @options.debug_level >= level
41
-
42
- message = +"#{ex.message} (#{ex.class})"
43
- message << "\n" << ex.backtrace.join("\n") unless ex.backtrace.nil?
44
- log(level: level, color: color) { message }
45
- end
27
+ def log_exception(ex, level: @options.debug_level, color: nil)
28
+ return unless @options.debug
29
+ return unless @options.debug_level >= level
46
30
 
31
+ log(level: level, color: color) { ex.full_message }
47
32
  end
48
33
  end
49
34
  end
data/lib/httpx/options.rb CHANGED
@@ -3,19 +3,22 @@
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
9
+ BUFFER_SIZE = 1 << 14
7
10
  WINDOW_SIZE = 1 << 14 # 16K
8
11
  MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
9
- CONNECT_TIMEOUT = 60
10
- OPERATION_TIMEOUT = 60
11
12
  KEEP_ALIVE_TIMEOUT = 20
12
13
  SETTINGS_TIMEOUT = 10
13
- READ_TIMEOUT = WRITE_TIMEOUT = REQUEST_TIMEOUT = Float::INFINITY
14
+ CLOSE_HANDSHAKE_TIMEOUT = 10
15
+ CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
16
+ REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
14
17
 
15
18
  # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
16
19
  ip_address_families = begin
17
20
  list = Socket.ip_address_list
18
- if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
21
+ if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? && !a.ipv6_unique_local? }
19
22
  [Socket::AF_INET6, Socket::AF_INET]
20
23
  else
21
24
  [Socket::AF_INET]
@@ -25,14 +28,19 @@ module HTTPX
25
28
  end
26
29
 
27
30
  DEFAULT_OPTIONS = {
31
+ :max_requests => Float::INFINITY,
28
32
  :debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
29
33
  :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
30
34
  :ssl => {},
31
35
  :http2_settings => { settings_enable_push: 0 },
32
36
  :fallback_protocol => "http/1.1",
37
+ :supported_compression_formats => %w[gzip deflate],
38
+ :decompress_response_body => true,
39
+ :compress_request_body => true,
33
40
  :timeout => {
34
41
  connect_timeout: CONNECT_TIMEOUT,
35
42
  settings_timeout: SETTINGS_TIMEOUT,
43
+ close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
36
44
  operation_timeout: OPERATION_TIMEOUT,
37
45
  keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
38
46
  read_timeout: READ_TIMEOUT,
@@ -41,6 +49,7 @@ module HTTPX
41
49
  },
42
50
  :headers => {},
43
51
  :window_size => WINDOW_SIZE,
52
+ :buffer_size => BUFFER_SIZE,
44
53
  :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
45
54
  :request_class => Class.new(Request),
46
55
  :response_class => Class.new(Response),
@@ -50,7 +59,6 @@ module HTTPX
50
59
  :connection_class => Class.new(Connection),
51
60
  :options_class => Class.new(self),
52
61
  :transport => nil,
53
- :transport_options => nil,
54
62
  :addresses => nil,
55
63
  :persistent => false,
56
64
  :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
@@ -58,28 +66,6 @@ module HTTPX
58
66
  :ip_families => ip_address_families,
59
67
  }.freeze
60
68
 
61
- begin
62
- module HashExtensions
63
- refine Hash do
64
- def >=(other)
65
- Hash[other] <= self
66
- end
67
-
68
- def <=(other)
69
- other = Hash[other]
70
- return false unless size <= other.size
71
-
72
- each do |k, v|
73
- v2 = other.fetch(k) { return false }
74
- return false unless v2 == v
75
- end
76
- true
77
- end
78
- end
79
- end
80
- using HashExtensions
81
- end unless Hash.method_defined?(:>=)
82
-
83
69
  class << self
84
70
  def new(options = {})
85
71
  # let enhanced options go through
@@ -98,38 +84,54 @@ module HTTPX
98
84
 
99
85
  attr_reader(optname)
100
86
  end
101
-
102
- def def_option(optname, *args, &block)
103
- if args.size.zero? && !block
104
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
105
- def option_#{optname}(v); v; end # def option_smth(v); v; end
106
- OUT
107
- return
108
- end
109
-
110
- deprecated_def_option(optname, *args, &block)
111
- end
112
-
113
- def deprecated_def_option(optname, layout = nil, &interpreter)
114
- warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
115
- "Define module OptionsMethods and `def option_#{optname}(val)` instead."
116
-
117
- if layout
118
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
119
- def option_#{optname}(value) # def option_origin(v)
120
- #{layout} # URI(v)
121
- end # end
122
- OUT
123
- elsif interpreter
124
- define_method(:"option_#{optname}") do |value|
125
- instance_exec(value, &interpreter)
126
- end
127
- end
128
- end
129
87
  end
130
88
 
89
+ # creates a new options instance from a given hash, which optionally define the following:
90
+ #
91
+ # :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
92
+ # :debug_level :: the log level of messages (can be 1, 2, or 3).
93
+ # :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
94
+ # :http2_settings :: a hash of options to be passed to a HTTP2Next::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
95
+ # :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
96
+ # like ALPN (defaults to <tt>"http/1.1"</tt>)
97
+ # :supported_compression_formats :: list of compressions supported by the transcoder layer (defaults to <tt>%w[gzip deflate]</tt>).
98
+ # :decompress_response_body :: whether to auto-decompress response body (defaults to <tt>true</tt>).
99
+ # :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
100
+ # :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
101
+ # <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>
102
+ # and <tt>:request_timeout</tt>
103
+ # :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
104
+ # :window_size :: number of bytes to read from a socket
105
+ # :buffer_size :: internal read and write buffer size in bytes
106
+ # :body_threshold_size :: maximum size in bytes of response payload that is buffered in memory.
107
+ # :request_class :: class used to instantiate a request
108
+ # :response_class :: class used to instantiate a response
109
+ # :headers_class :: class used to instantiate headers
110
+ # :request_body_class :: class used to instantiate a request body
111
+ # :response_body_class :: class used to instantiate a response body
112
+ # :connection_class :: class used to instantiate connections
113
+ # :options_class :: class used to instantiate options
114
+ # :transport :: type of transport to use (set to "unix" for UNIX sockets)
115
+ # :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
116
+ # paths should be used for UNIX sockets instead)
117
+ # :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
118
+ # :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
119
+ # :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
120
+ # using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
121
+ # :resolver_options :: hash of options passed to the resolver
122
+ # :ip_families :: which socket families are supported (system-dependent)
123
+ # :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
124
+ # :base_path :: path to prefix given relative paths with (ex: "/v2")
125
+ # :max_concurrent_requests :: max number of requests which can be set concurrently
126
+ # :max_requests :: max number of requests which can be made on socket before it reconnects.
127
+ # :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
128
+ # :form :: hash of array of key-values which will be form-or-multipart-encoded in requests body payload.
129
+ # :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
130
+ # :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
131
+ #
132
+ # This list of options are enhanced with each loaded plugin, see the plugin docs for details.
131
133
  def initialize(options = {})
132
- __initialize__(options)
134
+ do_initialize(options)
133
135
  freeze
134
136
  end
135
137
 
@@ -140,6 +142,7 @@ module HTTPX
140
142
  @timeout.freeze
141
143
  @headers.freeze
142
144
  @addresses.freeze
145
+ @supported_compression_formats.freeze
143
146
  end
144
147
 
145
148
  def option_origin(value)
@@ -155,14 +158,11 @@ module HTTPX
155
158
  end
156
159
 
157
160
  def option_timeout(value)
158
- timeouts = Hash[value]
159
-
160
- if timeouts.key?(:loop_timeout)
161
- warn ":loop_timeout is deprecated, use :operation_timeout instead"
162
- timeouts[:operation_timeout] = timeouts.delete(:loop_timeout)
163
- end
161
+ Hash[value]
162
+ end
164
163
 
165
- timeouts
164
+ def option_supported_compression_formats(value)
165
+ Array(value).map(&:to_s)
166
166
  end
167
167
 
168
168
  def option_max_concurrent_requests(value)
@@ -178,16 +178,31 @@ module HTTPX
178
178
  end
179
179
 
180
180
  def option_window_size(value)
181
- Integer(value)
181
+ value = Integer(value)
182
+
183
+ raise TypeError, ":window_size must be positive" unless value.positive?
184
+
185
+ value
186
+ end
187
+
188
+ def option_buffer_size(value)
189
+ value = Integer(value)
190
+
191
+ raise TypeError, ":buffer_size must be positive" unless value.positive?
192
+
193
+ value
182
194
  end
183
195
 
184
196
  def option_body_threshold_size(value)
185
- Integer(value)
197
+ bytes = Integer(value)
198
+ raise TypeError, ":body_threshold_size must be positive" unless bytes.positive?
199
+
200
+ bytes
186
201
  end
187
202
 
188
203
  def option_transport(value)
189
204
  transport = value.to_s
190
- raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
205
+ raise TypeError, "#{transport} is an unsupported transport type" unless %w[unix].include?(transport)
191
206
 
192
207
  transport
193
208
  end
@@ -204,50 +219,78 @@ module HTTPX
204
219
  params form json xml body ssl http2_settings
205
220
  request_class response_class headers_class request_body_class
206
221
  response_body_class connection_class options_class
207
- io fallback_protocol debug debug_level transport_options resolver_class resolver_options
222
+ io fallback_protocol debug debug_level resolver_class resolver_options
223
+ compress_request_body decompress_response_body
208
224
  persistent
209
225
  ].each do |method_name|
210
- def_option(method_name)
226
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
227
+ def option_#{method_name}(v); v; end # def option_smth(v); v; end
228
+ OUT
211
229
  end
212
230
 
213
- REQUEST_IVARS = %i[@params @form @xml @json @body].freeze
214
- private_constant :REQUEST_IVARS
231
+ REQUEST_BODY_IVARS = %i[@headers @params @form @xml @json @body].freeze
215
232
 
216
233
  def ==(other)
217
- ivars = instance_variables | other.instance_variables
234
+ super || options_equals?(other)
235
+ end
236
+
237
+ def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
238
+ # headers and other request options do not play a role, as they are
239
+ # relevant only for the request.
240
+ ivars = instance_variables - ignore_ivars
241
+ other_ivars = other.instance_variables - ignore_ivars
242
+
243
+ return false if ivars.size != other_ivars.size
244
+
245
+ return false if ivars.sort != other_ivars.sort
246
+
218
247
  ivars.all? do |ivar|
219
- case ivar
220
- when :@headers
221
- # currently, this is used to pick up an available matching connection.
222
- # the headers do not play a role, as they are relevant only for the request.
223
- true
224
- when *REQUEST_IVARS
225
- true
226
- else
227
- instance_variable_get(ivar) == other.instance_variable_get(ivar)
228
- end
248
+ instance_variable_get(ivar) == other.instance_variable_get(ivar)
229
249
  end
230
250
  end
231
251
 
252
+ OTHER_LOOKUP = ->(obj, k, ivar_map) {
253
+ case obj
254
+ when Hash
255
+ obj[ivar_map[k]]
256
+ else
257
+ obj.instance_variable_get(k)
258
+ end
259
+ }
232
260
  def merge(other)
233
- raise ArgumentError, "#{other} is not a valid set of options" unless other.respond_to?(:to_hash)
261
+ ivar_map = nil
262
+ other_ivars = case other
263
+ when Hash
264
+ ivar_map = other.keys.to_h { |k| [:"@#{k}", k] }
265
+ ivar_map.keys
266
+ else
267
+ other.instance_variables
268
+ end
234
269
 
235
- h2 = other.to_hash
236
- return self if h2.empty?
270
+ return self if other_ivars.empty?
237
271
 
238
- h1 = to_hash
272
+ return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == OTHER_LOOKUP[other, ivar, ivar_map] }
239
273
 
240
- return self if h1 >= h2
274
+ opts = dup
241
275
 
242
- merged = h1.merge(h2) do |_k, v1, v2|
243
- if v1.respond_to?(:merge) && v2.respond_to?(:merge)
244
- v1.merge(v2)
245
- else
246
- v2
276
+ other_ivars.each do |ivar|
277
+ v = OTHER_LOOKUP[other, ivar, ivar_map]
278
+
279
+ unless v
280
+ opts.instance_variable_set(ivar, v)
281
+ next
247
282
  end
283
+
284
+ v = opts.__send__(:"option_#{ivar[1..-1]}", v)
285
+
286
+ orig_v = instance_variable_get(ivar)
287
+
288
+ v = orig_v.merge(v) if orig_v.respond_to?(:merge) && v.respond_to?(:merge)
289
+
290
+ opts.instance_variable_set(ivar, v)
248
291
  end
249
292
 
250
- self.class.new(merged)
293
+ opts
251
294
  end
252
295
 
253
296
  def to_hash
@@ -256,30 +299,45 @@ module HTTPX
256
299
  end
257
300
  end
258
301
 
259
- if RUBY_VERSION > "2.4.0"
260
- def initialize_dup(other)
261
- instance_variables.each do |ivar|
262
- instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
263
- end
302
+ def extend_with_plugin_classes(pl)
303
+ if defined?(pl::RequestMethods) || defined?(pl::RequestClassMethods)
304
+ @request_class = @request_class.dup
305
+ @request_class.__send__(:include, pl::RequestMethods) if defined?(pl::RequestMethods)
306
+ @request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
264
307
  end
265
- else
266
- def initialize_dup(other)
267
- instance_variables.each do |ivar|
268
- value = other.instance_variable_get(ivar)
269
- value = case value
270
- when Symbol, Numeric, TrueClass, FalseClass
271
- value
272
- else
273
- value.dup
274
- end
275
- instance_variable_set(ivar, value)
276
- end
308
+ if defined?(pl::ResponseMethods) || defined?(pl::ResponseClassMethods)
309
+ @response_class = @response_class.dup
310
+ @response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
311
+ @response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
277
312
  end
313
+ if defined?(pl::HeadersMethods) || defined?(pl::HeadersClassMethods)
314
+ @headers_class = @headers_class.dup
315
+ @headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
316
+ @headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
317
+ end
318
+ if defined?(pl::RequestBodyMethods) || defined?(pl::RequestBodyClassMethods)
319
+ @request_body_class = @request_body_class.dup
320
+ @request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
321
+ @request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
322
+ end
323
+ if defined?(pl::ResponseBodyMethods) || defined?(pl::ResponseBodyClassMethods)
324
+ @response_body_class = @response_body_class.dup
325
+ @response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
326
+ @response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
327
+ end
328
+ if defined?(pl::ConnectionMethods)
329
+ @connection_class = @connection_class.dup
330
+ @connection_class.__send__(:include, pl::ConnectionMethods)
331
+ end
332
+ return unless defined?(pl::OptionsMethods)
333
+
334
+ @options_class = @options_class.dup
335
+ @options_class.__send__(:include, pl::OptionsMethods)
278
336
  end
279
337
 
280
338
  private
281
339
 
282
- def __initialize__(options = {})
340
+ def do_initialize(options = {})
283
341
  defaults = DEFAULT_OPTIONS.merge(options)
284
342
  defaults.each do |k, v|
285
343
  next if v.nil?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
3
+ require "httpx/base64"
4
4
 
5
5
  module HTTPX
6
6
  module Plugins
@@ -11,10 +11,6 @@ module HTTPX
11
11
  @password = password
12
12
  end
13
13
 
14
- def can_authenticate?(authenticate)
15
- authenticate && /Basic .*/.match?(authenticate)
16
- end
17
-
18
14
  def authenticate(*)
19
15
  "Basic #{Base64.strict_encode64("#{@user}:#{@password}")}"
20
16
  end