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
@@ -32,6 +32,15 @@ module HTTPX
32
32
  end
33
33
  end
34
34
 
35
+ module NativeResolverMethods
36
+ def transition(nextstate)
37
+ state = @state
38
+ val = super
39
+ meter_elapsed_time("Resolver::Native: #{state} -> #{nextstate}")
40
+ val
41
+ end
42
+ end
43
+
35
44
  module InstanceMethods
36
45
  def self.included(klass)
37
46
  klass.prepend TrackTimeMethods
@@ -42,6 +51,13 @@ module HTTPX
42
51
  meter_elapsed_time("Session: initializing...")
43
52
  super
44
53
  meter_elapsed_time("Session: initialized!!!")
54
+ resolver_type = @options.resolver_class
55
+ resolver_type = Resolver.resolver_for(resolver_type)
56
+ return unless resolver_type <= Resolver::Native
57
+
58
+ resolver_type.prepend TrackTimeMethods
59
+ resolver_type.prepend NativeResolverMethods
60
+ @options = @options.merge(resolver_class: resolver_type)
45
61
  end
46
62
 
47
63
  def close(*)
@@ -3,12 +3,12 @@
3
3
  module HTTPX
4
4
  module Plugins
5
5
  #
6
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#ntlm-authentication
6
+ # https://gitlab.com/os85/httpx/wikis/Auth#ntlm-auth
7
7
  #
8
8
  module NTLMAuth
9
9
  class << self
10
10
  def load_dependencies(_klass)
11
- require_relative "authentication/ntlm"
11
+ require_relative "auth/ntlm"
12
12
  end
13
13
 
14
14
  def extra_options(options)
@@ -25,11 +25,11 @@ module HTTPX
25
25
  end
26
26
 
27
27
  module InstanceMethods
28
- def ntlm_authentication(user, password, domain = nil)
28
+ def ntlm_auth(user, password, domain = nil)
29
29
  with(ntlm: Authentication::Ntlm.new(user, password, domain: domain))
30
30
  end
31
31
 
32
- alias_method :ntlm_auth, :ntlm_authentication
32
+ private
33
33
 
34
34
  def send_requests(*requests)
35
35
  requests.flat_map do |request|
@@ -39,6 +39,8 @@ module HTTPX
39
39
  request.headers["authorization"] = ntlm.negotiate
40
40
  probe_response = wrap { super(request).first }
41
41
 
42
+ return probe_response unless probe_response.is_a?(Response)
43
+
42
44
  if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
43
45
  request.transition(:idle)
44
46
  request.headers["authorization"] = ntlm.authenticate(request, probe_response.headers["www-authenticate"])
@@ -53,6 +55,6 @@ module HTTPX
53
55
  end
54
56
  end
55
57
  end
56
- register_plugin :ntlm_authentication, NTLMAuth
58
+ register_plugin :ntlm_auth, NTLMAuth
57
59
  end
58
60
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # https://gitlab.com/os85/httpx/wikis/OAuth
7
+ #
8
+ module OAuth
9
+ class << self
10
+ def load_dependencies(_klass)
11
+ require_relative "auth/basic"
12
+ end
13
+ end
14
+
15
+ SUPPORTED_GRANT_TYPES = %w[client_credentials refresh_token].freeze
16
+ SUPPORTED_AUTH_METHODS = %w[client_secret_basic client_secret_post].freeze
17
+
18
+ class OAuthSession
19
+ attr_reader :token_endpoint_auth_method, :grant_type, :client_id, :client_secret, :access_token, :refresh_token, :scope
20
+
21
+ def initialize(
22
+ issuer:,
23
+ client_id:,
24
+ client_secret:,
25
+ access_token: nil,
26
+ refresh_token: nil,
27
+ scope: nil,
28
+ token_endpoint: nil,
29
+ response_type: nil,
30
+ grant_type: nil,
31
+ token_endpoint_auth_method: "client_secret_basic"
32
+ )
33
+ @issuer = URI(issuer)
34
+ @client_id = client_id
35
+ @client_secret = client_secret
36
+ @token_endpoint = URI(token_endpoint) if token_endpoint
37
+ @response_type = response_type
38
+ @scope = case scope
39
+ when String
40
+ scope.split
41
+ when Array
42
+ scope
43
+ end
44
+ @access_token = access_token
45
+ @refresh_token = refresh_token
46
+ @token_endpoint_auth_method = String(token_endpoint_auth_method)
47
+ @grant_type = grant_type || (@refresh_token ? "refresh_token" : "client_credentials")
48
+
49
+ unless SUPPORTED_AUTH_METHODS.include?(@token_endpoint_auth_method)
50
+ raise Error, "#{@token_endpoint_auth_method} is not a supported auth method"
51
+ end
52
+
53
+ return if SUPPORTED_GRANT_TYPES.include?(@grant_type)
54
+
55
+ raise Error, "#{@grant_type} is not a supported grant type"
56
+ end
57
+
58
+ def token_endpoint
59
+ @token_endpoint || "#{@issuer}/token"
60
+ end
61
+
62
+ def load(http)
63
+ return unless @token_endpoint && @token_endpoint_auth_method && @grant_type && @scope
64
+
65
+ metadata = http.get("#{issuer}/.well-known/oauth-authorization-server").raise_for_status.json
66
+
67
+ @token_endpoint = metadata["token_endpoint"]
68
+ @scope = metadata["scopes_supported"]
69
+ @grant_type = Array(metadata["grant_types_supported"]).find { |gr| SUPPORTED_GRANT_TYPES.include?(gr) }
70
+ @token_endpoint_auth_method = Array(metadata["token_endpoint_auth_methods_supported"]).find do |am|
71
+ SUPPORTED_AUTH_METHODS.include?(am)
72
+ end
73
+ end
74
+
75
+ def merge(other)
76
+ obj = dup
77
+
78
+ case other
79
+ when OAuthSession
80
+ other.instance_variables.each do |ivar|
81
+ val = other.instance_variable_get(ivar)
82
+ next unless val
83
+
84
+ obj.instance_variable_set(ivar, val)
85
+ end
86
+ when Hash
87
+ other.each do |k, v|
88
+ obj.instance_variable_set(:"@#{k}", v) if obj.instance_variable_defined?(:"@#{k}")
89
+ end
90
+ end
91
+ obj
92
+ end
93
+ end
94
+
95
+ module OptionsMethods
96
+ def option_oauth_session(value)
97
+ case value
98
+ when Hash
99
+ OAuthSession.new(**value)
100
+ when OAuthSession
101
+ value
102
+ else
103
+ raise TypeError, ":oauth_session must be a #{OAuthSession}"
104
+ end
105
+ end
106
+ end
107
+
108
+ module InstanceMethods
109
+ def oauth_auth(**args)
110
+ with(oauth_session: OAuthSession.new(**args))
111
+ end
112
+
113
+ def with_access_token
114
+ oauth_session = @options.oauth_session
115
+
116
+ oauth_session.load(self)
117
+
118
+ grant_type = oauth_session.grant_type
119
+
120
+ headers = {}
121
+ form_post = { "grant_type" => grant_type, "scope" => Array(oauth_session.scope).join(" ") }.compact
122
+
123
+ # auth
124
+ case oauth_session.token_endpoint_auth_method
125
+ when "client_secret_basic"
126
+ headers["authorization"] = Authentication::Basic.new(oauth_session.client_id, oauth_session.client_secret).authenticate
127
+ when "client_secret_post"
128
+ form_post["client_id"] = oauth_session.client_id
129
+ form_post["client_secret"] = oauth_session.client_secret
130
+ end
131
+
132
+ case grant_type
133
+ when "client_credentials"
134
+ # do nothing
135
+ when "refresh_token"
136
+ form_post["refresh_token"] = oauth_session.refresh_token
137
+ end
138
+
139
+ token_request = build_request("POST", oauth_session.token_endpoint, headers: headers, form: form_post)
140
+ token_request.headers.delete("authorization") unless oauth_session.token_endpoint_auth_method == "client_secret_basic"
141
+
142
+ token_response = request(token_request)
143
+ token_response.raise_for_status
144
+
145
+ payload = token_response.json
146
+
147
+ access_token = payload["access_token"]
148
+ refresh_token = payload["refresh_token"]
149
+
150
+ with(oauth_session: oauth_session.merge(access_token: access_token, refresh_token: refresh_token))
151
+ end
152
+
153
+ def build_request(*, _)
154
+ request = super
155
+
156
+ return request if request.headers.key?("authorization")
157
+
158
+ oauth_session = @options.oauth_session
159
+
160
+ return request unless oauth_session && oauth_session.access_token
161
+
162
+ request.headers["authorization"] = "Bearer #{oauth_session.access_token}"
163
+
164
+ request
165
+ end
166
+ end
167
+ end
168
+ register_plugin :oauth, OAuth
169
+ end
170
+ end
@@ -15,7 +15,7 @@ module HTTPX
15
15
  # This plugin is also not recommendable when connecting to >9000 (like, a lot) different origins.
16
16
  # So when you use this, make sure that you don't fall into this trap.
17
17
  #
18
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Persistent
18
+ # https://gitlab.com/os85/httpx/wikis/Persistent
19
19
  #
20
20
  module Persistent
21
21
  def self.load_dependencies(klass)
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
-
5
3
  module HTTPX
6
4
  module Plugins
7
5
  module Proxy
8
6
  module HTTP
7
+ class << self
8
+ def extra_options(options)
9
+ options.merge(supported_proxy_protocols: options.supported_proxy_protocols + %w[http])
10
+ end
11
+ end
12
+
9
13
  module InstanceMethods
10
14
  def with_proxy_basic_auth(opts)
11
15
  with(proxy: opts.merge(scheme: "basic"))
@@ -23,6 +27,7 @@ module HTTPX
23
27
  response = super
24
28
 
25
29
  if response &&
30
+ response.is_a?(Response) &&
26
31
  response.status == 407 &&
27
32
  !request.headers.key?("proxy-authorization") &&
28
33
  response.headers.key?("proxy-authenticate")
@@ -33,7 +38,7 @@ module HTTPX
33
38
  request.transition(:idle)
34
39
  request.headers["proxy-authorization"] =
35
40
  connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
36
- connection.send(request)
41
+ send_request(request, connections)
37
42
  return
38
43
  end
39
44
  end
@@ -60,7 +65,7 @@ module HTTPX
60
65
  return unless @io.connected?
61
66
 
62
67
  @parser || begin
63
- @parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
68
+ @parser = self.class.parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
64
69
  parser = @parser
65
70
  parser.extend(ProxyParser)
66
71
  parser.on(:response, &method(:__http_on_connect))
@@ -71,7 +76,6 @@ module HTTPX
71
76
  else
72
77
  transition(:closing)
73
78
  transition(:closed)
74
- emit(:reset)
75
79
 
76
80
  parser.reset if @parser
77
81
  transition(:idle)
@@ -113,13 +117,14 @@ module HTTPX
113
117
 
114
118
  def __http_on_connect(request, response)
115
119
  @inflight -= 1
116
- if response.status == 200
120
+ if response.is_a?(Response) && response.status == 200
117
121
  req = @pending.first
118
122
  request_uri = req.uri
119
123
  @io = ProxySSL.new(@io, request_uri, @options)
120
124
  transition(:connected)
121
125
  throw(:called)
122
- elsif response.status == 407 &&
126
+ elsif response.is_a?(Response) &&
127
+ response.status == 407 &&
123
128
  !request.headers.key?("proxy-authorization") &&
124
129
  @options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
125
130
 
@@ -139,9 +144,9 @@ module HTTPX
139
144
 
140
145
  module ProxyParser
141
146
  def join_headline(request)
142
- return super if request.verb == :connect
147
+ return super if request.verb == "CONNECT"
143
148
 
144
- "#{request.verb.to_s.upcase} #{request.uri} HTTP/#{@version.join(".")}"
149
+ "#{request.verb} #{request.uri} HTTP/#{@version.join(".")}"
145
150
  end
146
151
 
147
152
  def set_protocol_headers(request)
@@ -159,7 +164,7 @@ module HTTPX
159
164
 
160
165
  class ConnectRequest < Request
161
166
  def initialize(uri, _options)
162
- super(:connect, uri, {})
167
+ super("CONNECT", uri, {})
163
168
  @headers.delete("accept")
164
169
  end
165
170
 
@@ -4,7 +4,7 @@ require "resolv"
4
4
  require "ipaddr"
5
5
 
6
6
  module HTTPX
7
- class Socks4Error < Error; end
7
+ class Socks4Error < HTTPProxyError; end
8
8
 
9
9
  module Plugins
10
10
  module Proxy
@@ -16,6 +16,12 @@ module HTTPX
16
16
 
17
17
  Error = Socks4Error
18
18
 
19
+ class << self
20
+ def extra_options(options)
21
+ options.merge(supported_proxy_protocols: options.supported_proxy_protocols + PROTOCOLS)
22
+ end
23
+ end
24
+
19
25
  module ConnectionMethods
20
26
  def interests
21
27
  if @state == :connecting
@@ -79,17 +85,13 @@ module HTTPX
79
85
  end
80
86
 
81
87
  class SocksParser
82
- include Callbacks
88
+ include HTTPX::Callbacks
83
89
 
84
90
  def initialize(buffer, options)
85
91
  @buffer = buffer
86
92
  @options = Options.new(options)
87
93
  end
88
94
 
89
- def timeout
90
- @options.timeout[:operation_timeout]
91
- end
92
-
93
95
  def close; end
94
96
 
95
97
  def consume(*); end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- class Socks5Error < Error; end
4
+ class Socks5Error < HTTPProxyError; end
5
5
 
6
6
  module Plugins
7
7
  module Proxy
@@ -18,8 +18,14 @@ module HTTPX
18
18
 
19
19
  Error = Socks5Error
20
20
 
21
- def self.load_dependencies(*)
22
- require_relative "../authentication/socks5"
21
+ class << self
22
+ def load_dependencies(*)
23
+ require_relative "../auth/socks5"
24
+ end
25
+
26
+ def extra_options(options)
27
+ options.merge(supported_proxy_protocols: options.supported_proxy_protocols + %w[socks5])
28
+ end
23
29
  end
24
30
 
25
31
  module ConnectionMethods
@@ -131,17 +137,13 @@ module HTTPX
131
137
  end
132
138
 
133
139
  class SocksParser
134
- include Callbacks
140
+ include HTTPX::Callbacks
135
141
 
136
142
  def initialize(buffer, options)
137
143
  @buffer = buffer
138
144
  @options = Options.new(options)
139
145
  end
140
146
 
141
- def timeout
142
- @options.timeout[:operation_timeout]
143
- end
144
-
145
147
  def close; end
146
148
 
147
149
  def consume(*); end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- class HTTPProxyError < Error; end
4
+ class HTTPProxyError < ConnectionError; end
5
5
 
6
6
  module Plugins
7
7
  #
@@ -12,12 +12,24 @@ module HTTPX
12
12
  # * Socks4/4a proxies
13
13
  # * Socks5 proxies
14
14
  #
15
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Proxy
15
+ # https://gitlab.com/os85/httpx/wikis/Proxy
16
16
  #
17
17
  module Proxy
18
18
  Error = HTTPProxyError
19
19
  PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
20
20
 
21
+ class << self
22
+ def configure(klass)
23
+ klass.plugin(:"proxy/http")
24
+ klass.plugin(:"proxy/socks4")
25
+ klass.plugin(:"proxy/socks5")
26
+ end
27
+
28
+ def extra_options(options)
29
+ options.merge(supported_proxy_protocols: [])
30
+ end
31
+ end
32
+
21
33
  class Parameters
22
34
  attr_reader :uri, :username, :password, :scheme
23
35
 
@@ -41,7 +53,7 @@ module HTTPX
41
53
 
42
54
  auth_scheme = scheme.to_s.capitalize
43
55
 
44
- require_relative "authentication/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
56
+ require_relative "auth/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
45
57
 
46
58
  @authenticator = Authentication.const_get(auth_scheme).new(@username, @password, **extra)
47
59
  end
@@ -77,54 +89,61 @@ module HTTPX
77
89
  end
78
90
  end
79
91
 
80
- class << self
81
- def configure(klass)
82
- klass.plugin(:"proxy/http")
83
- klass.plugin(:"proxy/socks4")
84
- klass.plugin(:"proxy/socks5")
85
- end
86
- end
87
-
88
92
  module OptionsMethods
89
93
  def option_proxy(value)
90
94
  value.is_a?(Parameters) ? value : Hash[value]
91
95
  end
96
+
97
+ def option_supported_proxy_protocols(value)
98
+ raise TypeError, ":supported_proxy_protocols must be an Array" unless value.is_a?(Array)
99
+
100
+ value.map(&:to_s)
101
+ end
92
102
  end
93
103
 
94
104
  module InstanceMethods
95
105
  private
96
106
 
97
- def proxy_uris(uri, options)
98
- @_proxy_uris ||= begin
99
- uris = options.proxy ? Array(options.proxy[:uri]) : []
100
- if uris.empty?
101
- uri = URI(uri).find_proxy
102
- uris << uri if uri
103
- end
104
- uris
105
- end
106
- return if @_proxy_uris.empty?
107
+ def find_connection(request, connections, options)
108
+ return super unless options.respond_to?(:proxy)
107
109
 
108
- proxy = options.proxy
110
+ uri = URI(request.uri)
109
111
 
110
- return { uri: uri.host } if proxy && proxy.key?(:no_proxy) && !Array(proxy[:no_proxy]).grep(uri.host).empty?
112
+ proxy_opts = if (next_proxy = uri.find_proxy)
113
+ { uri: next_proxy }
114
+ else
115
+ proxy = options.proxy
111
116
 
112
- proxy_opts = { uri: @_proxy_uris.first }
113
- proxy_opts = options.proxy.merge(proxy_opts) if options.proxy
114
- proxy_opts
115
- end
117
+ return super unless proxy
116
118
 
117
- def find_connection(request, connections, options)
118
- return super unless options.respond_to?(:proxy)
119
+ return super(request, connections, options.merge(proxy: nil)) unless proxy.key?(:uri)
119
120
 
120
- uri = URI(request.uri)
121
- next_proxy = proxy_uris(uri, options)
122
- raise Error, "Failed to connect to proxy" unless next_proxy
121
+ @_proxy_uris ||= Array(proxy[:uri])
122
+
123
+ next_proxy = @_proxy_uris.first
124
+ raise Error, "Failed to connect to proxy" unless next_proxy
125
+
126
+ next_proxy = URI(next_proxy)
127
+
128
+ raise Error,
129
+ "#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
123
130
 
124
- proxy = Parameters.new(**next_proxy) unless next_proxy[:uri] == uri.host
131
+ if proxy.key?(:no_proxy)
132
+
133
+ no_proxy = proxy[:no_proxy]
134
+ no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
135
+
136
+ return super(request, connections, options.merge(proxy: nil)) unless URI::Generic.use_proxy?(uri.host, next_proxy.host,
137
+ next_proxy.port, no_proxy)
138
+ end
139
+
140
+ proxy.merge(uri: next_proxy)
141
+ end
142
+
143
+ proxy = Parameters.new(**proxy_opts)
125
144
 
126
145
  proxy_options = options.merge(proxy: proxy)
127
- connection = pool.find_connection(uri, proxy_options) || build_connection(uri, proxy_options)
146
+ connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
128
147
  unless connections.nil? || connections.include?(connection)
129
148
  connections << connection
130
149
  set_connection_callbacks(connection, connections, options)
@@ -132,40 +151,24 @@ module HTTPX
132
151
  connection
133
152
  end
134
153
 
135
- def build_connection(uri, options)
136
- proxy = options.proxy
137
- return super unless proxy
138
-
139
- connection = options.connection_class.new("tcp", uri, options)
140
- catch(:coalesced) do
141
- pool.init_connection(connection, options)
142
- connection
143
- end
144
- end
145
-
146
154
  def fetch_response(request, connections, options)
147
155
  response = super
148
156
 
149
- if response.is_a?(ErrorResponse) &&
150
- __proxy_error?(response) && !@_proxy_uris.empty?
157
+ if response.is_a?(ErrorResponse) && proxy_error?(request, response)
151
158
  @_proxy_uris.shift
159
+
160
+ # return last error response if no more proxies to try
161
+ return response if @_proxy_uris.empty?
162
+
152
163
  log { "failed connecting to proxy, trying next..." }
153
164
  request.transition(:idle)
154
- connection = find_connection(request, connections, options)
155
- connections << connection unless connections.include?(connection)
156
- connection.send(request)
165
+ send_request(request, connections, options)
157
166
  return
158
167
  end
159
168
  response
160
169
  end
161
170
 
162
- def build_altsvc_connection(_, _, _, _, _, options)
163
- return if options.proxy
164
-
165
- super
166
- end
167
-
168
- def __proxy_error?(response)
171
+ def proxy_error?(_request, response)
169
172
  error = response.error
170
173
  case error
171
174
  when NativeResolveError
@@ -222,13 +225,6 @@ module HTTPX
222
225
  end
223
226
  end
224
227
 
225
- def send(request)
226
- return super unless @options.proxy
227
- return super unless connecting?
228
-
229
- @pending << request
230
- end
231
-
232
228
  def connecting?
233
229
  return super unless @options.proxy
234
230
 
@@ -250,13 +246,19 @@ module HTTPX
250
246
  return super unless @options.proxy
251
247
 
252
248
  @state = :open
253
- transition(:closing)
254
- transition(:closed)
249
+
250
+ super
255
251
  emit(:close)
256
252
  end
257
253
 
258
254
  private
259
255
 
256
+ def initialize_type(uri, options)
257
+ return super unless options.proxy
258
+
259
+ "tcp"
260
+ end
261
+
260
262
  def connect
261
263
  return super unless @options.proxy
262
264
 
@@ -284,7 +286,7 @@ module HTTPX
284
286
  register_plugin :proxy, Proxy
285
287
  end
286
288
 
287
- class ProxySSL < IO.registry["ssl"]
289
+ class ProxySSL < SSL
288
290
  def initialize(tcp, request_uri, options)
289
291
  @io = tcp.to_io
290
292
  super(request_uri, tcp.addresses, options)
@@ -8,7 +8,7 @@ module HTTPX
8
8
  # In order to benefit from this, requests are sent one at a time, so that
9
9
  # no push responses are received after corresponding request has been sent.
10
10
  #
11
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Server-Push
11
+ # https://gitlab.com/os85/httpx/wikis/Server-Push
12
12
  #
13
13
  module PushPromise
14
14
  def self.extra_options(options)
@@ -9,7 +9,7 @@ module HTTPX
9
9
  # * when the server is unavailable (503);
10
10
  # * when a 3xx request comes with a "retry-after" value
11
11
  #
12
- # https://gitlab.com/honeyryderchuck/httpx/wikis/RateLimiter
12
+ # https://gitlab.com/os85/httpx/wikis/Rate-Limiter
13
13
  #
14
14
  module RateLimiter
15
15
  class << self
@@ -23,6 +23,8 @@ module HTTPX
23
23
  end
24
24
 
25
25
  def retry_on_rate_limited_response(response)
26
+ return false unless response.is_a?(Response)
27
+
26
28
  status = response.status
27
29
 
28
30
  RATE_LIMIT_CODES.include?(status)