httpx 1.7.7 → 1.8.0
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.
- checksums.yaml +4 -4
- data/doc/release_notes/1_7_8.md +5 -0
- data/doc/release_notes/1_8_0.md +100 -0
- data/lib/httpx/adapters/datadog.rb +3 -1
- data/lib/httpx/connection/http1.rb +10 -1
- data/lib/httpx/connection/http2.rb +37 -4
- data/lib/httpx/connection.rb +76 -7
- data/lib/httpx/errors.rb +8 -1
- data/lib/httpx/io/tcp.rb +11 -1
- data/lib/httpx/options.rb +16 -4
- data/lib/httpx/parser/http1.rb +8 -2
- data/lib/httpx/plugins/auth.rb +52 -4
- data/lib/httpx/plugins/{response_cache → cache}/file_store.rb +1 -1
- data/lib/httpx/plugins/{response_cache → cache}/store.rb +1 -1
- data/lib/httpx/plugins/cache.rb +221 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +50 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +92 -0
- data/lib/httpx/plugins/oauth.rb +66 -14
- data/lib/httpx/plugins/proxy.rb +5 -0
- data/lib/httpx/plugins/response_cache.rb +26 -105
- data/lib/httpx/plugins/retries.rb +13 -5
- data/lib/httpx/plugins/server_sent_events.rb +158 -0
- data/lib/httpx/plugins/ssrf_filter.rb +16 -1
- data/lib/httpx/plugins/stream.rb +7 -3
- data/lib/httpx/plugins/tracing.rb +15 -4
- data/lib/httpx/request.rb +18 -1
- data/lib/httpx/resolver/cache/file.rb +56 -0
- data/lib/httpx/resolver/native.rb +14 -3
- data/lib/httpx/response/body.rb +4 -2
- data/lib/httpx/response.rb +9 -1
- data/lib/httpx/selector.rb +7 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +3 -0
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +11 -8
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +2 -0
- data/sig/io/tcp.rbs +2 -0
- data/sig/loggable.rbs +4 -0
- data/sig/options.rbs +25 -12
- data/sig/parser/http1.rbs +3 -1
- data/sig/plugins/auth/ntlm.rbs +1 -1
- data/sig/plugins/{response_cache → cache}/file_store.rbs +2 -2
- data/sig/plugins/{response_cache → cache}/store.rbs +2 -2
- data/sig/plugins/cache.rbs +69 -0
- data/sig/plugins/fiber_concurrency.rbs +4 -0
- data/sig/plugins/ntlm_v2_auth.rbs +36 -0
- data/sig/plugins/response_cache.rbs +13 -38
- data/sig/plugins/retries.rbs +5 -5
- data/sig/plugins/server_sent_events.rbs +45 -0
- data/sig/plugins/ssrf_filter.rbs +5 -1
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/webdav.rbs +1 -1
- data/sig/pool.rbs +2 -2
- data/sig/request.rbs +7 -3
- data/sig/resolver/cache/file.rbs +13 -0
- data/sig/resolver/entry.rbs +1 -1
- data/sig/resolver/https.rbs +3 -3
- data/sig/resolver/multi.rbs +1 -1
- data/sig/resolver/native.rbs +5 -5
- data/sig/resolver/resolver.rbs +1 -3
- data/sig/resolver/system.rbs +2 -2
- data/sig/resolver.rbs +3 -0
- data/sig/response.rbs +3 -0
- data/sig/selector.rbs +11 -8
- data/sig/timers.rbs +5 -5
- data/sig/transcoder/body.rbs +1 -1
- data/sig/transcoder/gzip.rbs +3 -2
- data/sig/transcoder/multipart.rbs +4 -1
- data/sig/transcoder/utils/deflater.rbs +2 -0
- data/sig/transcoder.rbs +2 -0
- data/sig/utils.rbs +1 -1
- metadata +19 -7
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds support for caching and reusing responses
|
|
7
|
+
#
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Cache
|
|
9
|
+
#
|
|
10
|
+
module Cache
|
|
11
|
+
class << self
|
|
12
|
+
def load_dependencies(*)
|
|
13
|
+
require_relative "cache/store"
|
|
14
|
+
require_relative "cache/file_store"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extra_options(options)
|
|
18
|
+
options.merge(
|
|
19
|
+
response_cache_store: :store,
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# adds support for the following options:
|
|
25
|
+
#
|
|
26
|
+
# :cache_key :: callable which receives a request and returns the corresponding cache key as a string
|
|
27
|
+
# (to be used by the cache store when storing cached responses)
|
|
28
|
+
# :cacheable_request :: callable which receives a request and returns whether this request can use a previously cached response,
|
|
29
|
+
# or for which a freshly retrieved response can be cached.
|
|
30
|
+
# :cacheable_response :: callable which receives a request and a (freshly retrieved) response and returns whether the response
|
|
31
|
+
# can be cached.
|
|
32
|
+
# :valid_cached_response :: callable which receives a request and a (previously cached) response and returns whether the response
|
|
33
|
+
# can still be used / returned to the caller.
|
|
34
|
+
# :response_cache_store :: object where cached responses are fetch from or stored in; defaults to <tt>:store</tt> (in-memory
|
|
35
|
+
# cache), can be set to <tt>:file_store</tt> (file system cache store) as well, or any object which
|
|
36
|
+
# abides by the Cache Store Interface
|
|
37
|
+
#
|
|
38
|
+
# The Cache Store Interface requires implementation of the following methods:
|
|
39
|
+
#
|
|
40
|
+
# * +#get(request) -> response or nil+
|
|
41
|
+
# * +#set(request, response) -> void+
|
|
42
|
+
# * +#clear() -> void+)
|
|
43
|
+
#
|
|
44
|
+
module OptionsMethods
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def option_cache_key(v)
|
|
48
|
+
raise TypeError, "`:cache_key` must be a callable" unless v.respond_to?(:call)
|
|
49
|
+
|
|
50
|
+
v
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def option_cacheable_request(v)
|
|
54
|
+
raise TypeError, "`:cacheable_request` must be a callable" unless v.respond_to?(:call)
|
|
55
|
+
|
|
56
|
+
v
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def option_cacheable_response(v)
|
|
60
|
+
raise TypeError, "`:cacheable_response` must be a callable" unless v.respond_to?(:call)
|
|
61
|
+
|
|
62
|
+
v
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def option_valid_cached_response(v)
|
|
66
|
+
raise TypeError, "`:valid_cached_response` must be a callable" unless v.respond_to?(:call)
|
|
67
|
+
|
|
68
|
+
v
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def option_response_cache_store(value)
|
|
72
|
+
case value
|
|
73
|
+
when :store
|
|
74
|
+
Store.new
|
|
75
|
+
when :file_store
|
|
76
|
+
FileStore.new
|
|
77
|
+
else
|
|
78
|
+
value
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module InstanceMethods
|
|
84
|
+
# wipes out all cached responses from the cache store.
|
|
85
|
+
def clear_response_cache
|
|
86
|
+
@options.response_cache_store.clear
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_request(*)
|
|
90
|
+
request = super
|
|
91
|
+
return request unless cacheable_request?(request)
|
|
92
|
+
|
|
93
|
+
prepare_cache(request)
|
|
94
|
+
|
|
95
|
+
request
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def send_request(request, *)
|
|
101
|
+
return request if request.response
|
|
102
|
+
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def fetch_response(request, *)
|
|
107
|
+
response = super
|
|
108
|
+
|
|
109
|
+
return unless response
|
|
110
|
+
|
|
111
|
+
if cacheable_request?(request) && cacheable_response?(request, response) && !response.cached?
|
|
112
|
+
log { "caching response for #{request.uri}..." }
|
|
113
|
+
request.options.response_cache_store.set(request, response)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
response
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# whether +request+ can use cached responses.
|
|
120
|
+
def cacheable_request?(request)
|
|
121
|
+
return false unless (call = request.options.cacheable_request)
|
|
122
|
+
|
|
123
|
+
call[request]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# whether the retrieved +response+ can be cached.
|
|
127
|
+
def cacheable_response?(request, response)
|
|
128
|
+
return false unless (call = request.options.cacheable_response)
|
|
129
|
+
|
|
130
|
+
call[request, response]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# whether the cached +cached_response+ is still valid for the current +request+
|
|
134
|
+
def valid_cached_response?(request, cached_response)
|
|
135
|
+
return false unless (call = request.options.valid_cached_response)
|
|
136
|
+
|
|
137
|
+
call[request, cached_response]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# will either assign a still-fresh cached response to +request+, or set up its HTTP
|
|
141
|
+
# cache invalidation headers in case it's not fresh anymore.
|
|
142
|
+
def prepare_cache(request)
|
|
143
|
+
cached_response = retrieve_cached_response(request)
|
|
144
|
+
|
|
145
|
+
return unless cached_response && valid_cached_response?(request, cached_response)
|
|
146
|
+
|
|
147
|
+
request.cached_response = nil
|
|
148
|
+
|
|
149
|
+
# if the cached response is still usable, we use it
|
|
150
|
+
cached_response.body.rewind
|
|
151
|
+
cached_response = cached_response.dup
|
|
152
|
+
cached_response.mark_as_cached!
|
|
153
|
+
request.response = cached_response
|
|
154
|
+
request.emit_response(cached_response)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# calls the cache store to retrieve the cached response for +request+. Caches it
|
|
158
|
+
# for convenience of subplugins in order to minimize overhead of retrieval (which may
|
|
159
|
+
# involve network).
|
|
160
|
+
def retrieve_cached_response(request)
|
|
161
|
+
request.cached_response ||= request.options.response_cache_store.get(request)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
module RequestMethods
|
|
166
|
+
# points to a previously cached Response corresponding to this request.
|
|
167
|
+
attr_accessor :cached_response
|
|
168
|
+
|
|
169
|
+
def initialize(*)
|
|
170
|
+
super
|
|
171
|
+
@cached_response = nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def merge_headers(*)
|
|
175
|
+
super
|
|
176
|
+
@response_cache_key = nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# returns a unique cache key as a String identifying this request
|
|
180
|
+
def response_cache_key
|
|
181
|
+
return unless (call = @options.cache_key)
|
|
182
|
+
|
|
183
|
+
call[self]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
module ResponseMethods
|
|
188
|
+
attr_writer :original_request
|
|
189
|
+
|
|
190
|
+
def initialize(*)
|
|
191
|
+
super
|
|
192
|
+
@cached = false
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# a copy of the request this response was originally cached from
|
|
196
|
+
def original_request
|
|
197
|
+
@original_request || @request
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# whether this Response was duplicated from a previously {RequestMethods#cached_response}.
|
|
201
|
+
def cached?
|
|
202
|
+
@cached
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# sets this Response as being duplicated from a previously cached response.
|
|
206
|
+
def mark_as_cached!
|
|
207
|
+
@cached = true
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
module ResponseBodyMethods
|
|
212
|
+
def decode_chunk(chunk)
|
|
213
|
+
return chunk if @response.cached?
|
|
214
|
+
|
|
215
|
+
super
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
register_plugin :cache, Cache
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -76,12 +76,44 @@ module HTTPX
|
|
|
76
76
|
super
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
def on_io_error(e)
|
|
80
|
+
return super unless e.is_a?(IOError) && e.message.include?("stream closed in another thread")
|
|
81
|
+
|
|
82
|
+
# @fiber-switch-guard
|
|
83
|
+
# sockets closed during fiber scheduler switches are raised in separate fibers than the fiber the
|
|
84
|
+
# socket may be used in. this check verifies that this is actually about this socket.
|
|
85
|
+
return unless to_io.closed?
|
|
86
|
+
|
|
87
|
+
if @state == :closing
|
|
88
|
+
# @fiber-switch-guard
|
|
89
|
+
# if the connection is reused across fibers, the socket may have been closed in the other fiber
|
|
90
|
+
# and switched here during the process, so continue what it was doing and transition to closed
|
|
91
|
+
# via #call.
|
|
92
|
+
call
|
|
93
|
+
elsif !backlog?
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def on_connect_error(e)
|
|
99
|
+
return super unless e.is_a?(IOError) && e.message.include?("stream closed in another thread")
|
|
100
|
+
|
|
101
|
+
# @fiber-switch-guard
|
|
102
|
+
# sockets closed during fiber scheduler switches are raised in separate fibers than the fiber the
|
|
103
|
+
# socket may be used in. this check verifies that this is actually about this socket.
|
|
104
|
+
return unless to_io.closed? && !backlog?
|
|
82
105
|
|
|
83
106
|
super
|
|
84
107
|
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# checks whether the connection has any pending request (which the connection itself may
|
|
112
|
+
# have stored, or it may be somewhere in the parser).
|
|
113
|
+
def backlog?
|
|
114
|
+
@pending.any? ||
|
|
115
|
+
(@parser && (@parser.pending.any? || @parser.requests.any?))
|
|
116
|
+
end
|
|
85
117
|
end
|
|
86
118
|
|
|
87
119
|
module HTTP1Methods
|
|
@@ -168,6 +200,21 @@ module HTTPX
|
|
|
168
200
|
|
|
169
201
|
super
|
|
170
202
|
end
|
|
203
|
+
|
|
204
|
+
def disconnect
|
|
205
|
+
return unless @connections.all?(&:current_context?)
|
|
206
|
+
|
|
207
|
+
super
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def on_io_error(e)
|
|
211
|
+
# TODO: return super if this is not stream clsed in another thread
|
|
212
|
+
|
|
213
|
+
log { "IO Erroring: #{e.message}, current:#{@name}, queries:#{@queries.size}" }
|
|
214
|
+
return unless @name
|
|
215
|
+
|
|
216
|
+
super
|
|
217
|
+
end
|
|
171
218
|
end
|
|
172
219
|
|
|
173
220
|
module ResolverSystemMethods
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
# https://gitlab.com/os85/httpx/wikis/Auth#ntlm-v2-auth
|
|
6
|
+
module NtlmV2Auth
|
|
7
|
+
class << self
|
|
8
|
+
def load_dependencies(klass)
|
|
9
|
+
require "rubyntlm"
|
|
10
|
+
klass.plugin(:auth)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def extra_options(options)
|
|
14
|
+
options.merge(max_concurrent_requests: 1)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Authenticator
|
|
19
|
+
def initialize(user, password, domain: nil)
|
|
20
|
+
@user = user
|
|
21
|
+
@password = password
|
|
22
|
+
@domain = domain
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def can_authenticate?(www_authenticate)
|
|
26
|
+
www_authenticate && /NTLM/i.match?(www_authenticate)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def negotiate
|
|
30
|
+
t1 = Net::NTLM::Message::Type1.new
|
|
31
|
+
t1.domain = @domain if @domain
|
|
32
|
+
"NTLM #{t1.encode64}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def authenticate(_request, www_authenticate)
|
|
36
|
+
challenge_b64 = www_authenticate[/NTLM (.+)/i, 1]
|
|
37
|
+
t2 = Net::NTLM::Message.decode64(challenge_b64)
|
|
38
|
+
t3 = t2.response(
|
|
39
|
+
{ user: @user, password: @password, domain: @domain },
|
|
40
|
+
ntlmv2: true
|
|
41
|
+
)
|
|
42
|
+
"NTLM #{t3.encode64}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module OptionsMethods
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def option_ntlm(value)
|
|
50
|
+
raise TypeError, ":ntlm must be a #{Authenticator}" unless value.is_a?(NtlmV2Auth::Authenticator)
|
|
51
|
+
|
|
52
|
+
value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module InstanceMethods
|
|
57
|
+
def ntlm_auth(user, password, domain = nil)
|
|
58
|
+
with(ntlm: Authenticator.new(user, password, domain: domain))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def send_requests(*requests)
|
|
64
|
+
requests.flat_map do |request|
|
|
65
|
+
ntlm = request.options.ntlm
|
|
66
|
+
|
|
67
|
+
if ntlm
|
|
68
|
+
request.authorize(ntlm.negotiate)
|
|
69
|
+
probe_response = wrap { super(request).first }
|
|
70
|
+
|
|
71
|
+
return probe_response unless probe_response.is_a?(Response)
|
|
72
|
+
|
|
73
|
+
if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
74
|
+
request.transition(:idle)
|
|
75
|
+
request.unauthorize!
|
|
76
|
+
request.authorize(ntlm.authenticate(request,
|
|
77
|
+
probe_response.headers["www-authenticate"]).encode("utf-8"))
|
|
78
|
+
super(request)
|
|
79
|
+
else
|
|
80
|
+
probe_response
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
super(request)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
register_plugin :ntlm_v2_auth, NtlmV2Auth
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/httpx/plugins/oauth.rb
CHANGED
|
@@ -12,6 +12,7 @@ module HTTPX
|
|
|
12
12
|
module OAuth
|
|
13
13
|
class << self
|
|
14
14
|
def load_dependencies(klass)
|
|
15
|
+
require "monitor"
|
|
15
16
|
require_relative "auth/basic"
|
|
16
17
|
klass.plugin(:auth)
|
|
17
18
|
end
|
|
@@ -33,8 +34,6 @@ module HTTPX
|
|
|
33
34
|
# Implements the bulk of functionality and maintains the state associated with the
|
|
34
35
|
# management of the the lifecycle of an OAuth session.
|
|
35
36
|
class OAuthSession
|
|
36
|
-
attr_reader :access_token, :refresh_token
|
|
37
|
-
|
|
38
37
|
def initialize(
|
|
39
38
|
issuer:,
|
|
40
39
|
client_id:,
|
|
@@ -62,8 +61,8 @@ module HTTPX
|
|
|
62
61
|
@refresh_token = refresh_token
|
|
63
62
|
@token_endpoint_auth_method = String(token_endpoint_auth_method) if token_endpoint_auth_method
|
|
64
63
|
@grant_type = grant_type || (@refresh_token ? "refresh_token" : "client_credentials")
|
|
65
|
-
@
|
|
66
|
-
@
|
|
64
|
+
@expires_at = nil
|
|
65
|
+
@token_mon = Monitor.new
|
|
67
66
|
|
|
68
67
|
unless @token_endpoint_auth_method.nil? || SUPPORTED_AUTH_METHODS.include?(@token_endpoint_auth_method)
|
|
69
68
|
raise Error, "#{@token_endpoint_auth_method} is not a supported auth method"
|
|
@@ -84,8 +83,24 @@ module HTTPX
|
|
|
84
83
|
@token_endpoint_auth_method || "client_secret_basic"
|
|
85
84
|
end
|
|
86
85
|
|
|
86
|
+
def expires_at
|
|
87
|
+
@token_mon.synchronize { @expires_at }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def access_token
|
|
91
|
+
@token_mon.synchronize do
|
|
92
|
+
if (expires_at = @expires_at) && expires_at < Time.now.to_i
|
|
93
|
+
reset!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
@access_token
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
87
100
|
def reset!
|
|
88
|
-
@
|
|
101
|
+
@token_mon.synchronize do
|
|
102
|
+
@access_token = @expires_at = nil
|
|
103
|
+
end
|
|
89
104
|
end
|
|
90
105
|
|
|
91
106
|
# when not available, it uses the +http+ object to request new access and refresh tokens.
|
|
@@ -117,9 +132,11 @@ module HTTPX
|
|
|
117
132
|
when "client_credentials"
|
|
118
133
|
# do nothing
|
|
119
134
|
when "refresh_token"
|
|
120
|
-
|
|
135
|
+
ref_token = refresh_token
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
raise Error, "cannot use the `\"refresh_token\"` grant type without a refresh token" unless ref_token
|
|
138
|
+
|
|
139
|
+
form_post["refresh_token"] = ref_token
|
|
123
140
|
end
|
|
124
141
|
|
|
125
142
|
# POST /token
|
|
@@ -138,8 +155,13 @@ module HTTPX
|
|
|
138
155
|
|
|
139
156
|
payload = token_response.json
|
|
140
157
|
|
|
141
|
-
@
|
|
142
|
-
|
|
158
|
+
@token_mon.synchronize do
|
|
159
|
+
@refresh_token = payload.fetch("refresh_token", @refresh_token)
|
|
160
|
+
if (expires_in = payload["expires_in"])
|
|
161
|
+
@expires_at = Time.now.to_i + Integer(expires_in)
|
|
162
|
+
end
|
|
163
|
+
@access_token = payload["access_token"]
|
|
164
|
+
end
|
|
143
165
|
end
|
|
144
166
|
|
|
145
167
|
# TODO: remove this after deprecating the `:oauth_session` option
|
|
@@ -164,17 +186,23 @@ module HTTPX
|
|
|
164
186
|
|
|
165
187
|
private
|
|
166
188
|
|
|
189
|
+
def refresh_token
|
|
190
|
+
@token_mon.synchronize { @refresh_token }
|
|
191
|
+
end
|
|
192
|
+
|
|
167
193
|
# uses +http+ to fetch for the oauth server metadata.
|
|
168
194
|
def load(http)
|
|
169
195
|
return if @grant_type && @scope
|
|
170
196
|
|
|
171
197
|
metadata = http.skip_auth_header { http.get("#{@issuer}/.well-known/oauth-authorization-server").raise_for_status.json }
|
|
172
198
|
|
|
173
|
-
@
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
199
|
+
@token_mon.synchronize do
|
|
200
|
+
@token_endpoint = metadata["token_endpoint"]
|
|
201
|
+
@scope = metadata["scopes_supported"]
|
|
202
|
+
@grant_type = Array(metadata["grant_types_supported"]).find { |gr| SUPPORTED_GRANT_TYPES.include?(gr) }
|
|
203
|
+
@token_endpoint_auth_method = Array(metadata["token_endpoint_auth_methods_supported"]).find do |am|
|
|
204
|
+
SUPPORTED_AUTH_METHODS.include?(am)
|
|
205
|
+
end
|
|
178
206
|
end
|
|
179
207
|
nil
|
|
180
208
|
end
|
|
@@ -239,6 +267,11 @@ module HTTPX
|
|
|
239
267
|
|
|
240
268
|
@oauth_session.reset!
|
|
241
269
|
@oauth_session.fetch_access_token(self)
|
|
270
|
+
if (expires_at = @oauth_session.expires_at)
|
|
271
|
+
@auth_header_value_mtx.synchronize do
|
|
272
|
+
@auth_header_expires_at = expires_at
|
|
273
|
+
end
|
|
274
|
+
end
|
|
242
275
|
end
|
|
243
276
|
|
|
244
277
|
# TODO: deprecate
|
|
@@ -249,9 +282,18 @@ module HTTPX
|
|
|
249
282
|
other_session = dup # : instance
|
|
250
283
|
oauth_session = other_session.oauth_session
|
|
251
284
|
oauth_session.fetch_access_token(other_session)
|
|
285
|
+
if (expires_at = oauth_session.expires_at)
|
|
286
|
+
@auth_header_expires_at = expires_at
|
|
287
|
+
end
|
|
252
288
|
other_session
|
|
253
289
|
end
|
|
254
290
|
|
|
291
|
+
def reset_auth_header_value!
|
|
292
|
+
super.tap do
|
|
293
|
+
@oauth_session.reset if @oauth_session
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
255
297
|
private
|
|
256
298
|
|
|
257
299
|
def generate_auth_token
|
|
@@ -260,6 +302,16 @@ module HTTPX
|
|
|
260
302
|
@oauth_session.fetch_access_token(self)
|
|
261
303
|
end
|
|
262
304
|
|
|
305
|
+
def set_auth_header_expires_at(_)
|
|
306
|
+
return super unless @oauth_session
|
|
307
|
+
|
|
308
|
+
expires_at = @oauth_session.expires_at
|
|
309
|
+
|
|
310
|
+
return super unless expires_at
|
|
311
|
+
|
|
312
|
+
@auth_header_expires_at = expires_at
|
|
313
|
+
end
|
|
314
|
+
|
|
263
315
|
def dynamic_auth_token?(_)
|
|
264
316
|
@oauth_session
|
|
265
317
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
3
5
|
module HTTPX
|
|
4
6
|
class ProxyError < ConnectionError; end
|
|
5
7
|
|
|
@@ -65,6 +67,9 @@ module HTTPX
|
|
|
65
67
|
|
|
66
68
|
return unless @scheme
|
|
67
69
|
|
|
70
|
+
@username = CGI.unescape(@username) if @username
|
|
71
|
+
@password = CGI.unescape(@password) if @password
|
|
72
|
+
|
|
68
73
|
@authenticator = load_authenticator(@scheme, @username, @password, **extra)
|
|
69
74
|
end
|
|
70
75
|
|