httpx 0.20.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) 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 +5 -5
  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_19_8.md +1 -1
  15. data/doc/release_notes/0_20_0.md +2 -2
  16. data/doc/release_notes/0_20_1.md +5 -0
  17. data/doc/release_notes/0_20_2.md +7 -0
  18. data/doc/release_notes/0_20_3.md +6 -0
  19. data/doc/release_notes/0_20_4.md +17 -0
  20. data/doc/release_notes/0_20_5.md +3 -0
  21. data/doc/release_notes/0_21_0.md +96 -0
  22. data/doc/release_notes/0_21_1.md +12 -0
  23. data/doc/release_notes/0_22_0.md +13 -0
  24. data/doc/release_notes/0_22_1.md +11 -0
  25. data/doc/release_notes/0_22_2.md +5 -0
  26. data/doc/release_notes/0_22_3.md +55 -0
  27. data/doc/release_notes/0_22_4.md +6 -0
  28. data/doc/release_notes/0_22_5.md +6 -0
  29. data/doc/release_notes/0_23_0.md +42 -0
  30. data/doc/release_notes/0_23_1.md +5 -0
  31. data/doc/release_notes/0_23_2.md +5 -0
  32. data/doc/release_notes/0_23_3.md +6 -0
  33. data/doc/release_notes/0_23_4.md +5 -0
  34. data/doc/release_notes/0_24_0.md +48 -0
  35. data/doc/release_notes/0_24_1.md +12 -0
  36. data/doc/release_notes/0_24_2.md +12 -0
  37. data/doc/release_notes/0_24_3.md +12 -0
  38. data/doc/release_notes/0_24_4.md +18 -0
  39. data/doc/release_notes/0_24_5.md +6 -0
  40. data/doc/release_notes/0_24_6.md +5 -0
  41. data/doc/release_notes/0_24_7.md +10 -0
  42. data/doc/release_notes/1_0_0.md +60 -0
  43. data/doc/release_notes/1_0_1.md +5 -0
  44. data/doc/release_notes/1_0_2.md +7 -0
  45. data/doc/release_notes/1_1_0.md +32 -0
  46. data/doc/release_notes/1_1_1.md +17 -0
  47. data/doc/release_notes/1_1_2.md +12 -0
  48. data/doc/release_notes/1_1_3.md +18 -0
  49. data/doc/release_notes/1_1_4.md +6 -0
  50. data/doc/release_notes/1_1_5.md +12 -0
  51. data/doc/release_notes/1_2_0.md +49 -0
  52. data/doc/release_notes/1_2_1.md +6 -0
  53. data/doc/release_notes/1_2_2.md +10 -0
  54. data/doc/release_notes/1_2_3.md +16 -0
  55. data/doc/release_notes/1_2_4.md +8 -0
  56. data/doc/release_notes/1_2_5.md +7 -0
  57. data/doc/release_notes/1_2_6.md +13 -0
  58. data/doc/release_notes/1_3_0.md +18 -0
  59. data/doc/release_notes/1_3_1.md +17 -0
  60. data/lib/httpx/adapters/datadog.rb +215 -122
  61. data/lib/httpx/adapters/faraday.rb +145 -107
  62. data/lib/httpx/adapters/sentry.rb +26 -7
  63. data/lib/httpx/adapters/webmock.rb +34 -18
  64. data/lib/httpx/altsvc.rb +63 -26
  65. data/lib/httpx/base64.rb +27 -0
  66. data/lib/httpx/buffer.rb +12 -0
  67. data/lib/httpx/callbacks.rb +5 -3
  68. data/lib/httpx/chainable.rb +54 -39
  69. data/lib/httpx/connection/http1.rb +75 -44
  70. data/lib/httpx/connection/http2.rb +31 -38
  71. data/lib/httpx/connection.rb +287 -117
  72. data/lib/httpx/domain_name.rb +10 -13
  73. data/lib/httpx/errors.rb +52 -2
  74. data/lib/httpx/extensions.rb +24 -131
  75. data/lib/httpx/io/ssl.rb +83 -77
  76. data/lib/httpx/io/tcp.rb +48 -71
  77. data/lib/httpx/io/udp.rb +18 -52
  78. data/lib/httpx/io/unix.rb +10 -15
  79. data/lib/httpx/io.rb +3 -9
  80. data/lib/httpx/loggable.rb +4 -19
  81. data/lib/httpx/options.rb +176 -118
  82. data/lib/httpx/parser/http1.rb +4 -0
  83. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  84. data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
  85. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  86. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  87. data/lib/httpx/plugins/auth.rb +25 -0
  88. data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
  89. data/lib/httpx/plugins/aws_sigv4.rb +12 -9
  90. data/lib/httpx/plugins/basic_auth.rb +29 -0
  91. data/lib/httpx/plugins/brotli.rb +50 -0
  92. data/lib/httpx/plugins/callbacks.rb +91 -0
  93. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  94. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  95. data/lib/httpx/plugins/circuit_breaker.rb +148 -0
  96. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  97. data/lib/httpx/plugins/cookies.rb +30 -17
  98. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
  99. data/lib/httpx/plugins/expect.rb +21 -14
  100. data/lib/httpx/plugins/follow_redirects.rb +140 -41
  101. data/lib/httpx/plugins/grpc/call.rb +2 -3
  102. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  103. data/lib/httpx/plugins/grpc/message.rb +7 -37
  104. data/lib/httpx/plugins/grpc.rb +36 -29
  105. data/lib/httpx/plugins/h2c.rb +26 -19
  106. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  107. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  108. data/lib/httpx/plugins/oauth.rb +175 -0
  109. data/lib/httpx/plugins/persistent.rb +1 -1
  110. data/lib/httpx/plugins/proxy/http.rb +23 -13
  111. data/lib/httpx/plugins/proxy/socks4.rb +9 -7
  112. data/lib/httpx/plugins/proxy/socks5.rb +11 -9
  113. data/lib/httpx/plugins/proxy.rb +80 -61
  114. data/lib/httpx/plugins/push_promise.rb +1 -1
  115. data/lib/httpx/plugins/rate_limiter.rb +5 -1
  116. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  117. data/lib/httpx/plugins/response_cache/store.rb +62 -25
  118. data/lib/httpx/plugins/response_cache.rb +105 -12
  119. data/lib/httpx/plugins/retries.rb +87 -17
  120. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  121. data/lib/httpx/plugins/stream.rb +27 -23
  122. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  123. data/lib/httpx/plugins/upgrade.rb +8 -10
  124. data/lib/httpx/plugins/webdav.rb +80 -0
  125. data/lib/httpx/pool/synch_pool.rb +93 -0
  126. data/lib/httpx/pool.rb +102 -27
  127. data/lib/httpx/punycode.rb +9 -291
  128. data/lib/httpx/request/body.rb +154 -0
  129. data/lib/httpx/request.rb +130 -146
  130. data/lib/httpx/resolver/https.rb +62 -27
  131. data/lib/httpx/resolver/multi.rb +9 -13
  132. data/lib/httpx/resolver/native.rb +192 -76
  133. data/lib/httpx/resolver/resolver.rb +34 -9
  134. data/lib/httpx/resolver/system.rb +16 -11
  135. data/lib/httpx/resolver.rb +38 -16
  136. data/lib/httpx/response/body.rb +242 -0
  137. data/lib/httpx/response/buffer.rb +96 -0
  138. data/lib/httpx/response.rb +159 -217
  139. data/lib/httpx/selector.rb +9 -4
  140. data/lib/httpx/session.rb +137 -89
  141. data/lib/httpx/session_extensions.rb +4 -1
  142. data/lib/httpx/timers.rb +34 -8
  143. data/lib/httpx/transcoder/body.rb +0 -2
  144. data/lib/httpx/transcoder/chunker.rb +0 -1
  145. data/lib/httpx/transcoder/deflate.rb +37 -0
  146. data/lib/httpx/transcoder/form.rb +52 -33
  147. data/lib/httpx/transcoder/gzip.rb +74 -0
  148. data/lib/httpx/transcoder/json.rb +21 -8
  149. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  150. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
  151. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  152. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  153. data/lib/httpx/transcoder/multipart.rb +17 -0
  154. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  155. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  156. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  157. data/lib/httpx/transcoder/xml.rb +52 -0
  158. data/lib/httpx/transcoder.rb +5 -6
  159. data/lib/httpx/utils.rb +36 -16
  160. data/lib/httpx/version.rb +1 -1
  161. data/lib/httpx.rb +12 -14
  162. data/sig/altsvc.rbs +33 -0
  163. data/sig/buffer.rbs +2 -1
  164. data/sig/callbacks.rbs +3 -3
  165. data/sig/chainable.rbs +11 -9
  166. data/sig/connection/http1.rbs +8 -7
  167. data/sig/connection/http2.rbs +19 -19
  168. data/sig/connection.rbs +64 -24
  169. data/sig/errors.rbs +22 -3
  170. data/sig/httpx.rbs +5 -4
  171. data/sig/io/ssl.rbs +27 -0
  172. data/sig/io/tcp.rbs +60 -0
  173. data/sig/io/udp.rbs +20 -0
  174. data/sig/io/unix.rbs +27 -0
  175. data/sig/io.rbs +6 -0
  176. data/sig/options.rbs +32 -22
  177. data/sig/parser/http1.rbs +1 -1
  178. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  179. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  180. data/sig/plugins/auth.rbs +13 -0
  181. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  182. data/sig/plugins/brotli.rbs +22 -0
  183. data/sig/plugins/callbacks.rbs +38 -0
  184. data/sig/plugins/circuit_breaker.rbs +71 -0
  185. data/sig/plugins/compression.rbs +7 -5
  186. data/sig/plugins/cookies/jar.rbs +2 -2
  187. data/sig/plugins/cookies.rbs +2 -0
  188. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  189. data/sig/plugins/follow_redirects.rbs +18 -4
  190. data/sig/plugins/grpc/call.rbs +19 -0
  191. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  192. data/sig/plugins/grpc/message.rbs +17 -0
  193. data/sig/plugins/grpc.rbs +7 -32
  194. data/sig/plugins/h2c.rbs +1 -1
  195. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  196. data/sig/plugins/oauth.rbs +54 -0
  197. data/sig/plugins/proxy/http.rbs +3 -0
  198. data/sig/plugins/proxy/socks4.rbs +9 -6
  199. data/sig/plugins/proxy/socks5.rbs +10 -6
  200. data/sig/plugins/proxy/ssh.rbs +1 -1
  201. data/sig/plugins/proxy.rbs +13 -5
  202. data/sig/plugins/push_promise.rbs +3 -3
  203. data/sig/plugins/rate_limiter.rbs +1 -1
  204. data/sig/plugins/response_cache.rbs +36 -7
  205. data/sig/plugins/retries.rbs +30 -8
  206. data/sig/plugins/stream.rbs +24 -17
  207. data/sig/plugins/upgrade.rbs +5 -3
  208. data/sig/pool.rbs +10 -7
  209. data/sig/request/body.rbs +38 -0
  210. data/sig/request.rbs +15 -24
  211. data/sig/resolver/https.rbs +8 -3
  212. data/sig/resolver/native.rbs +17 -4
  213. data/sig/resolver/resolver.rbs +8 -6
  214. data/sig/resolver/system.rbs +2 -0
  215. data/sig/resolver.rbs +9 -5
  216. data/sig/response/body.rbs +53 -0
  217. data/sig/response/buffer.rbs +24 -0
  218. data/sig/response.rbs +24 -39
  219. data/sig/selector.rbs +1 -1
  220. data/sig/session.rbs +29 -18
  221. data/sig/timers.rbs +18 -8
  222. data/sig/transcoder/body.rbs +4 -3
  223. data/sig/transcoder/deflate.rbs +11 -0
  224. data/sig/transcoder/form.rbs +5 -3
  225. data/sig/transcoder/gzip.rbs +24 -0
  226. data/sig/transcoder/json.rbs +8 -3
  227. data/sig/{plugins → transcoder}/multipart.rbs +15 -19
  228. data/sig/transcoder/utils/body_reader.rbs +15 -0
  229. data/sig/transcoder/utils/deflater.rbs +29 -0
  230. data/sig/transcoder/utils/inflater.rbs +12 -0
  231. data/sig/transcoder/xml.rbs +22 -0
  232. data/sig/transcoder.rbs +24 -9
  233. data/sig/utils.rbs +8 -2
  234. metadata +163 -41
  235. data/lib/httpx/plugins/authentication.rb +0 -20
  236. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  237. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  238. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  239. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  240. data/lib/httpx/plugins/compression.rb +0 -164
  241. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  242. data/lib/httpx/plugins/multipart.rb +0 -84
  243. data/lib/httpx/registry.rb +0 -85
  244. data/sig/plugins/authentication.rbs +0 -11
  245. data/sig/plugins/compression/brotli.rbs +0 -21
  246. data/sig/plugins/compression/deflate.rbs +0 -17
  247. data/sig/plugins/compression/gzip.rbs +0 -29
  248. data/sig/registry.rbs +0 -12
  249. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  250. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -51,8 +51,6 @@ module HTTPX
51
51
  # non-canonical domain.
52
52
  attr_reader :domain
53
53
 
54
- DOT = "." # :nodoc:
55
-
56
54
  class << self
57
55
  def new(domain)
58
56
  return domain if domain.is_a?(self)
@@ -63,8 +61,12 @@ module HTTPX
63
61
  # Normalizes a _domain_ using the Punycode algorithm as necessary.
64
62
  # The result will be a downcased, ASCII-only string.
65
63
  def normalize(domain)
66
- domain = domain.ascii_only? ? domain : domain.chomp(DOT).unicode_normalize(:nfc)
67
- 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
68
70
  end
69
71
  end
70
72
 
@@ -73,7 +75,7 @@ module HTTPX
73
75
  def initialize(hostname)
74
76
  hostname = String(hostname)
75
77
 
76
- raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(DOT)
78
+ raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(".")
77
79
 
78
80
  begin
79
81
  @ipaddr = IPAddr.new(hostname)
@@ -84,7 +86,7 @@ module HTTPX
84
86
  end
85
87
 
86
88
  @hostname = DomainName.normalize(hostname)
87
- tld = if (last_dot = @hostname.rindex(DOT))
89
+ tld = if (last_dot = @hostname.rindex("."))
88
90
  @hostname[(last_dot + 1)..-1]
89
91
  else
90
92
  @hostname
@@ -94,7 +96,7 @@ module HTTPX
94
96
  @domain = if last_dot
95
97
  # fallback - accept cookies down to second level
96
98
  # cf. http://www.dkim-reputation.org/regdom-libs/
97
- if (penultimate_dot = @hostname.rindex(DOT, last_dot - 1))
99
+ if (penultimate_dot = @hostname.rindex(".", last_dot - 1))
98
100
  @hostname[(penultimate_dot + 1)..-1]
99
101
  else
100
102
  @hostname
@@ -126,17 +128,12 @@ module HTTPX
126
128
  @domain && self <= domain && domain <= @domain
127
129
  end
128
130
 
129
- # def ==(other)
130
- # other = DomainName.new(other)
131
- # other.hostname == @hostname
132
- # end
133
-
134
131
  def <=>(other)
135
132
  other = DomainName.new(other)
136
133
  othername = other.hostname
137
134
  if othername == @hostname
138
135
  0
139
- elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] == DOT
136
+ elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] == "."
140
137
  # The other is higher
141
138
  -1
142
139
  else
data/lib/httpx/errors.rb CHANGED
@@ -1,18 +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
 
9
+ class ConnectionError < Error; end
10
+
11
+ # Error raised when there was a timeout. Its subclasses allow for finer-grained
12
+ # control of which timeout happened.
8
13
  class TimeoutError < Error
14
+ # The timeout value which caused this error to be raised.
9
15
  attr_reader :timeout
10
16
 
17
+ # initializes the timeout exception with the +timeout+ causing the error, and the
18
+ # error +message+ for it.
11
19
  def initialize(timeout, message)
12
20
  @timeout = timeout
13
21
  super(message)
14
22
  end
15
23
 
24
+ # clones this error into a HTTPX::ConnectionTimeoutError.
16
25
  def to_connection_error
17
26
  ex = ConnectTimeoutError.new(@timeout, message)
18
27
  ex.set_backtrace(backtrace)
@@ -20,19 +29,52 @@ module HTTPX
20
29
  end
21
30
  end
22
31
 
23
- class TotalTimeoutError < TimeoutError; end
24
-
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.
39
+ class RequestTimeoutError < TimeoutError
40
+ # The HTTPX::Request request object this exception refers to.
41
+ attr_reader :request
42
+
43
+ # initializes the exception with the +request+ and +response+ it refers to, and the
44
+ # +timeout+ causing the error, and the
45
+ def initialize(request, response, timeout)
46
+ @request = request
47
+ @response = response
48
+ super(timeout, "Timed out after #{timeout} seconds")
49
+ end
50
+
51
+ def marshal_dump
52
+ [message]
53
+ end
54
+ end
55
+
56
+ # Error raised when there was a timeout while receiving a response from the server.
57
+ class ReadTimeoutError < RequestTimeoutError; end
58
+
59
+ # Error raised when there was a timeout while sending a request from the server.
60
+ class WriteTimeoutError < RequestTimeoutError; end
61
+
62
+ # Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
27
63
  class SettingsTimeoutError < TimeoutError; end
28
64
 
65
+ # Error raised when there was a timeout while resolving a domain to an IP.
29
66
  class ResolveTimeoutError < TimeoutError; end
30
67
 
68
+ # Error raised when there was an error while resolving a domain to an IP.
31
69
  class ResolveError < Error; end
32
70
 
71
+ # Error raised when there was an error while resolving a domain to an IP
72
+ # using a HTTPX::Resolver::Native resolver.
33
73
  class NativeResolveError < ResolveError
34
74
  attr_reader :connection, :host
35
75
 
76
+ # initializes the exception with the +connection+ it refers to, the +host+ domain
77
+ # which failed to resolve, and the error +message+.
36
78
  def initialize(connection, host, message = "Can't resolve #{host}")
37
79
  @connection = connection
38
80
  @host = host
@@ -40,18 +82,26 @@ module HTTPX
40
82
  end
41
83
  end
42
84
 
85
+ # The exception class for HTTP responses with 4xx or 5xx status.
43
86
  class HTTPError < Error
87
+ # The HTTPX::Response response object this exception refers to.
44
88
  attr_reader :response
45
89
 
90
+ # Creates the instance and assigns the HTTPX::Response +response+.
46
91
  def initialize(response)
47
92
  @response = response
48
93
  super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
49
94
  end
50
95
 
96
+ # The HTTP response status.
97
+ #
98
+ # error.status #=> 404
51
99
  def status
52
100
  @response.status
53
101
  end
54
102
  end
55
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.
56
106
  class MisdirectedRequestError < HTTPError; end
57
107
  end
@@ -3,132 +3,40 @@
3
3
  require "uri"
4
4
 
5
5
  module HTTPX
6
- unless Method.method_defined?(:curry)
7
-
8
- # Backport
9
- #
10
- # Ruby 2.1 and lower implement curry only for Procs.
11
- #
12
- # Why not using Refinements? Because they don't work for Method (tested with ruby 2.1.9).
13
- #
14
- module CurryMethods
15
- # Backport for the Method#curry method, which is part of ruby core since 2.2 .
16
- #
17
- def curry(*args)
18
- to_proc.curry(*args)
19
- end
20
- end
21
- Method.__send__(:include, CurryMethods)
22
- end
23
-
24
- unless String.method_defined?(:+@)
25
- # Backport for +"", to initialize unfrozen strings from the string literal.
26
- #
27
- module LiteralStringExtensions
28
- def +@
29
- frozen? ? dup : self
30
- end
31
- end
32
- String.__send__(:include, LiteralStringExtensions)
33
- end
34
-
35
- unless Numeric.method_defined?(:positive?)
36
- # Ruby 2.3 Backport (Numeric#positive?)
37
- #
38
- module PosMethods
39
- def positive?
40
- self > 0
41
- end
42
- end
43
- Numeric.__send__(:include, PosMethods)
44
- end
45
-
46
- unless Numeric.method_defined?(:negative?)
47
- # Ruby 2.3 Backport (Numeric#negative?)
48
- #
49
- module NegMethods
50
- def negative?
51
- self < 0
52
- end
53
- end
54
- Numeric.__send__(:include, NegMethods)
55
- end
56
-
57
- module StringExtensions
58
- refine String do
59
- def delete_suffix!(suffix)
60
- suffix = Backports.coerce_to_str(suffix)
61
- chomp! if frozen?
62
- len = suffix.length
63
- if len > 0 && index(suffix, -len)
64
- self[-len..-1] = ''
65
- self
66
- else
67
- nil
68
- end
69
- end unless String.method_defined?(:delete_suffix!)
70
- end
71
- end
72
-
73
- module HashExtensions
74
- refine Hash do
75
- def compact
76
- h = {}
77
- each do |key, value|
78
- h[key] = value unless value == nil
79
- end
80
- h
81
- end unless Hash.method_defined?(:compact)
82
- end
83
- end
84
-
85
6
  module ArrayExtensions
86
- refine Array do
87
-
88
- def filter_map
89
- return to_enum(:filter_map) unless block_given?
90
-
91
- each_with_object([]) do |item, res|
92
- processed = yield(item)
93
- res << processed if processed
7
+ module FilterMap
8
+ refine Array do
9
+ # Ruby 2.7 backport
10
+ def filter_map
11
+ return to_enum(:filter_map) unless block_given?
12
+
13
+ each_with_object([]) do |item, res|
14
+ processed = yield(item)
15
+ res << processed if processed
16
+ end
94
17
  end
95
18
  end unless Array.method_defined?(:filter_map)
96
-
97
- def sum(accumulator = 0, &block)
98
- values = block_given? ? map(&block) : self
99
- values.inject(accumulator, :+)
100
- end unless Array.method_defined?(:sum)
101
19
  end
102
- end
103
-
104
- module IOExtensions
105
- refine IO do
106
- # provides a fallback for rubies where IO#wait isn't implemented,
107
- # but IO#wait_readable and IO#wait_writable are.
108
- def wait(timeout = nil, _mode = :read_write)
109
- r, w = IO.select([self], [self], nil, timeout)
110
-
111
- return unless r || w
112
-
113
- self
114
- end unless IO.method_defined?(:wait) && IO.instance_method(:wait).arity == 2
115
- end
116
- end
117
20
 
118
- module RegexpExtensions
119
- # If you wonder why this is there: the oauth feature uses a refinement to enhance the
120
- # Regexp class locally with #match? , but this is never tested, because ActiveSupport
121
- # monkey-patches the same method... Please ActiveSupport, stop being so intrusive!
122
- # :nocov:
123
- refine(Regexp) do
124
- def match?(*args)
125
- !match(*args).nil?
126
- end
21
+ module Intersect
22
+ refine Array do
23
+ # Ruby 3.1 backport
24
+ def intersect?(arr)
25
+ if size < arr.size
26
+ smaller = self
27
+ else
28
+ smaller, arr = arr, self
29
+ end
30
+ (arr & smaller).size > 0
31
+ end
32
+ end unless Array.method_defined?(:intersect?)
127
33
  end
128
34
  end
129
35
 
130
36
  module URIExtensions
37
+ # uri 0.11 backport, ships with ruby 3.1
131
38
  refine URI::Generic do
39
+
132
40
  def non_ascii_hostname
133
41
  @non_ascii_hostname
134
42
  end
@@ -146,21 +54,6 @@ module HTTPX
146
54
  def origin
147
55
  "#{scheme}://#{authority}"
148
56
  end unless URI::HTTP.method_defined?(:origin)
149
-
150
- def altsvc_match?(uri)
151
- uri = URI.parse(uri)
152
-
153
- origin == uri.origin || begin
154
- case scheme
155
- when "h2"
156
- (uri.scheme == "https" || uri.scheme == "h2") &&
157
- host == uri.host &&
158
- (port || default_port) == (uri.port || uri.default_port)
159
- else
160
- false
161
- end
162
- end
163
- end
164
57
  end
165
58
  end
166
59
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -4,26 +4,48 @@ require "openssl"
4
4
 
5
5
  module HTTPX
6
6
  TLSError = OpenSSL::SSL::SSLError
7
- IPRegex = Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex)
8
7
 
9
8
  class SSL < TCP
10
- using RegexpExtensions unless Regexp.method_defined?(:match?)
9
+ # rubocop:disable Style/MutableConstant
10
+ TLS_OPTIONS = { alpn_protocols: %w[h2 http/1.1].freeze }
11
+ # https://github.com/jruby/jruby-openssl/issues/284
12
+ TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby"
13
+ # rubocop:enable Style/MutableConstant
14
+ TLS_OPTIONS.freeze
11
15
 
12
- TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
13
- { alpn_protocols: %w[h2 http/1.1].freeze }.freeze
14
- else
15
- {}.freeze
16
- end
16
+ attr_writer :ssl_session
17
17
 
18
18
  def initialize(_, _, options)
19
19
  super
20
- @ctx = OpenSSL::SSL::SSLContext.new
20
+
21
21
  ctx_options = TLS_OPTIONS.merge(options.ssl)
22
22
  @sni_hostname = ctx_options.delete(:hostname) || @hostname
23
- @ctx.set_params(ctx_options) unless ctx_options.empty?
24
- @state = :negotiated if @keep_open
25
23
 
26
- @hostname_is_ip = IPRegex.match?(@sni_hostname)
24
+ if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
25
+ # externally initiated ssl socket
26
+ @ctx = @io.context
27
+ @state = :negotiated
28
+ else
29
+ @ctx = OpenSSL::SSL::SSLContext.new
30
+ @ctx.set_params(ctx_options) unless ctx_options.empty?
31
+ unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
32
+ @ctx.session_cache_mode =
33
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
34
+ end
35
+
36
+ yield(self) if block_given?
37
+ end
38
+
39
+ @verify_hostname = @ctx.verify_hostname
40
+ end
41
+
42
+ if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
43
+ def session_new_cb(&pr)
44
+ @ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
45
+ end
46
+ else
47
+ # session_new_cb not implemented under JRuby
48
+ def session_new_cb; end
27
49
  end
28
50
 
29
51
  def protocol
@@ -32,6 +54,20 @@ module HTTPX
32
54
  super
33
55
  end
34
56
 
57
+ if RUBY_ENGINE == "jruby"
58
+ # in jruby, alpn_protocol may return ""
59
+ # https://github.com/jruby/jruby-openssl/issues/287
60
+ def protocol
61
+ proto = @io.alpn_protocol
62
+
63
+ return super if proto.nil? || proto.empty?
64
+
65
+ proto
66
+ rescue StandardError
67
+ super
68
+ end
69
+ end
70
+
35
71
  def can_verify_peer?
36
72
  @ctx.verify_mode == OpenSSL::SSL::VERIFY_PEER
37
73
  end
@@ -43,85 +79,54 @@ module HTTPX
43
79
  OpenSSL::SSL.verify_certificate_identity(@io.peer_cert, host)
44
80
  end
45
81
 
46
- def close
47
- super
48
- # allow reconnections
49
- # connect only works if initial @io is a socket
50
- @io = @io.io if @io.respond_to?(:io)
51
- end
52
-
53
82
  def connected?
54
83
  @state == :negotiated
55
84
  end
56
85
 
86
+ def expired?
87
+ super || ssl_session_expired?
88
+ end
89
+
90
+ def ssl_session_expired?
91
+ @ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
92
+ end
93
+
57
94
  def connect
58
95
  super
59
96
  return if @state == :negotiated ||
60
97
  @state != :connected
61
98
 
62
99
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
100
+ if (hostname_is_ip = (@ip == @sni_hostname))
101
+ # IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
102
+ @sni_hostname = @ip.to_string
103
+ # IP addresses in SNI is not valid per RFC 6066, section 3.
104
+ @ctx.verify_hostname = false
105
+ end
106
+
63
107
  @io = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
64
- @io.hostname = @sni_hostname unless @hostname_is_ip
108
+
109
+ @io.hostname = @sni_hostname unless hostname_is_ip
110
+ @io.session = @ssl_session unless ssl_session_expired?
65
111
  @io.sync_close = true
66
112
  end
67
113
  try_ssl_connect
68
114
  end
69
115
 
70
- if RUBY_VERSION < "2.3"
71
- # :nocov:
72
- def try_ssl_connect
73
- @io.connect_nonblock
74
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
75
- transition(:negotiated)
76
- @interests = :w
77
- rescue ::IO::WaitReadable
116
+ def try_ssl_connect
117
+ ret = @io.connect_nonblock(exception: false)
118
+ log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
119
+ case ret
120
+ when :wait_readable
78
121
  @interests = :r
79
- rescue ::IO::WaitWritable
122
+ return
123
+ when :wait_writable
80
124
  @interests = :w
125
+ return
81
126
  end
82
-
83
- def read(_, buffer)
84
- super
85
- rescue ::IO::WaitWritable
86
- buffer.clear
87
- 0
88
- end
89
-
90
- def write(*)
91
- super
92
- rescue ::IO::WaitReadable
93
- 0
94
- end
95
- # :nocov:
96
- else
97
- def try_ssl_connect
98
- case @io.connect_nonblock(exception: false)
99
- when :wait_readable
100
- @interests = :r
101
- return
102
- when :wait_writable
103
- @interests = :w
104
- return
105
- end
106
- @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
107
- transition(:negotiated)
108
- @interests = :w
109
- end
110
-
111
- # :nocov:
112
- if OpenSSL::VERSION < "2.0.6"
113
- def read(size, buffer)
114
- @io.read_nonblock(size, buffer)
115
- buffer.bytesize
116
- rescue ::IO::WaitReadable,
117
- ::IO::WaitWritable
118
- buffer.clear
119
- 0
120
- rescue EOFError
121
- nil
122
- end
123
- end
124
- # :nocov:
127
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
128
+ transition(:negotiated)
129
+ @interests = :w
125
130
  end
126
131
 
127
132
  private
@@ -130,6 +135,7 @@ module HTTPX
130
135
  case nextstate
131
136
  when :negotiated
132
137
  return unless @state == :connected
138
+
133
139
  when :closed
134
140
  return unless @state == :negotiated ||
135
141
  @state == :connected
@@ -145,12 +151,12 @@ module HTTPX
145
151
  "#{super}\n\n" \
146
152
  "SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
147
153
  "ALPN, server accepted to use #{protocol}\n" \
148
- "Server certificate:\n" \
149
- " subject: #{server_cert.subject}\n" \
150
- " start date: #{server_cert.not_before}\n" \
151
- " expire date: #{server_cert.not_after}\n" \
152
- " issuer: #{server_cert.issuer}\n" \
153
- " SSL certificate verify ok."
154
+ "Server certificate:\n " \
155
+ "subject: #{server_cert.subject}\n " \
156
+ "start date: #{server_cert.not_before}\n " \
157
+ "expire date: #{server_cert.not_after}\n " \
158
+ "issuer: #{server_cert.issuer}\n " \
159
+ "SSL certificate verify ok."
154
160
  end
155
161
  end
156
162
  end