patient_http 1.6.1 → 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/ARCHITECTURE.md +18 -8
- data/CHANGELOG.md +43 -0
- data/README.md +564 -508
- data/VERSION +1 -1
- data/lib/patient_http/callback_args.rb +53 -48
- data/lib/patient_http/callback_validator.rb +11 -7
- data/lib/patient_http/class_helper.rb +6 -7
- data/lib/patient_http/client.rb +53 -26
- data/lib/patient_http/client_pool.rb +134 -39
- data/lib/patient_http/completion_executor.rb +20 -20
- data/lib/patient_http/configuration.rb +405 -119
- data/lib/patient_http/connection_endpoint.rb +150 -0
- data/lib/patient_http/encryptor.rb +28 -18
- data/lib/patient_http/error.rb +24 -18
- data/lib/patient_http/external_storage.rb +42 -38
- data/lib/patient_http/http_error.rb +30 -26
- data/lib/patient_http/http_headers.rb +35 -29
- data/lib/patient_http/immediate_retries.rb +98 -0
- data/lib/patient_http/inline_task_handler.rb +15 -10
- data/lib/patient_http/lifecycle_manager.rb +39 -40
- data/lib/patient_http/outgoing_request.rb +25 -23
- data/lib/patient_http/payload.rb +28 -26
- data/lib/patient_http/payload_store/active_record_store.rb +31 -34
- data/lib/patient_http/payload_store/base.rb +42 -46
- data/lib/patient_http/payload_store/file_store.rb +22 -26
- data/lib/patient_http/payload_store/redis_store.rb +28 -34
- data/lib/patient_http/payload_store/s3_store.rb +25 -28
- data/lib/patient_http/payload_store.rb +2 -0
- data/lib/patient_http/processor.rb +111 -79
- data/lib/patient_http/processor_observer.rb +65 -59
- data/lib/patient_http/rails/engine.rb +13 -8
- data/lib/patient_http/redirect_error.rb +50 -41
- data/lib/patient_http/redirect_helper.rb +38 -38
- data/lib/patient_http/request.rb +117 -46
- data/lib/patient_http/request_error.rb +47 -42
- data/lib/patient_http/request_helper.rb +178 -119
- data/lib/patient_http/request_preparer.rb +13 -10
- data/lib/patient_http/request_task.rb +115 -84
- data/lib/patient_http/request_template.rb +119 -64
- data/lib/patient_http/response.rb +58 -52
- data/lib/patient_http/response_reader.rb +166 -118
- data/lib/patient_http/secret_manager.rb +34 -30
- data/lib/patient_http/secret_reference.rb +33 -26
- data/lib/patient_http/synchronous_executor.rb +84 -95
- data/lib/patient_http/task_handler.rb +23 -19
- data/lib/patient_http/time_helper.rb +8 -8
- data/lib/patient_http.rb +327 -186
- data/patient_http.gemspec +3 -2
- metadata +21 -5
data/lib/patient_http.rb
CHANGED
|
@@ -12,34 +12,48 @@ require "socket"
|
|
|
12
12
|
require "securerandom"
|
|
13
13
|
require "logger"
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# Runs HTTP requests on an async I/O processor and passes each result to a
|
|
16
|
+
# callback service.
|
|
16
17
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# - Error handling with typed errors
|
|
18
|
+
# The processor runs in a dedicated thread and uses Ruby's fiber scheduler, so
|
|
19
|
+
# one thread can run hundreds of requests at the same time. A job system
|
|
20
|
+
# integration gem, such as `patient_http-sidekiq` or `patient_http-solid_queue`,
|
|
21
|
+
# runs the processor and calls the callbacks in background jobs.
|
|
22
22
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# @example Make a request
|
|
24
|
+
# PatientHttp.get(
|
|
25
|
+
# "https://api.example.com/users/123",
|
|
26
|
+
# callback: FetchUserCallback,
|
|
27
|
+
# callback_args: {user_id: 123}
|
|
28
|
+
# )
|
|
25
29
|
module PatientHttp
|
|
26
|
-
# Raised when
|
|
30
|
+
# Raised when a request is enqueued on a processor that isn't running.
|
|
27
31
|
class NotRunningError < StandardError; end
|
|
28
32
|
|
|
33
|
+
# Raised when a request is enqueued on a processor that is at
|
|
34
|
+
# `max_connections`.
|
|
29
35
|
class MaxCapacityError < StandardError; end
|
|
30
36
|
|
|
37
|
+
# Raised when a response body is larger than `max_response_size`.
|
|
31
38
|
class ResponseTooLargeError < StandardError; end
|
|
32
39
|
|
|
33
|
-
# Raised when
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
40
|
+
# Raised when no data arrives from the server for `read_timeout` seconds. It
|
|
41
|
+
# is a subclass of `Async::TimeoutError`, so the error type of the
|
|
42
|
+
# {RequestError} is `:timeout`. Use the class name to tell it apart from the
|
|
43
|
+
# timeout for the full request.
|
|
44
|
+
class ReadTimeoutError < Async::TimeoutError; end
|
|
45
|
+
|
|
46
|
+
# Raised when a request names a processor that isn't configured. The job
|
|
47
|
+
# system then retries the job instead of dropping it.
|
|
37
48
|
class UnknownProcessorError < StandardError; end
|
|
38
49
|
|
|
39
|
-
#
|
|
40
|
-
# A 300 response is followed only when
|
|
50
|
+
# The redirect status codes that are followed when the response has a
|
|
51
|
+
# `Location` header. A 300 response is followed only when `Location` names the
|
|
52
|
+
# server's preferred choice.
|
|
41
53
|
FOLLOWABLE_REDIRECT_STATUSES = [300, 301, 302, 303, 307, 308].freeze
|
|
42
54
|
|
|
55
|
+
# The gem version.
|
|
56
|
+
|
|
43
57
|
VERSION = File.read(File.join(__dir__, "../VERSION")).strip
|
|
44
58
|
|
|
45
59
|
# Autoload utility modules
|
|
@@ -54,11 +68,13 @@ module PatientHttp
|
|
|
54
68
|
autoload :ClientPool, File.join(__dir__, "patient_http/client_pool")
|
|
55
69
|
autoload :CompletionExecutor, File.join(__dir__, "patient_http/completion_executor")
|
|
56
70
|
autoload :Configuration, File.join(__dir__, "patient_http/configuration")
|
|
71
|
+
autoload :ConnectionEndpoint, File.join(__dir__, "patient_http/connection_endpoint")
|
|
57
72
|
autoload :Encryptor, File.join(__dir__, "patient_http/encryptor")
|
|
58
73
|
autoload :Error, File.join(__dir__, "patient_http/error")
|
|
59
74
|
autoload :ExternalStorage, File.join(__dir__, "patient_http/external_storage")
|
|
60
75
|
autoload :HttpError, File.join(__dir__, "patient_http/http_error")
|
|
61
76
|
autoload :HttpHeaders, File.join(__dir__, "patient_http/http_headers")
|
|
77
|
+
autoload :ImmediateRetries, File.join(__dir__, "patient_http/immediate_retries")
|
|
62
78
|
autoload :InlineTaskHandler, File.join(__dir__, "patient_http/inline_task_handler")
|
|
63
79
|
autoload :LifecycleManager, File.join(__dir__, "patient_http/lifecycle_manager")
|
|
64
80
|
autoload :OutgoingRequest, File.join(__dir__, "patient_http/outgoing_request")
|
|
@@ -89,38 +105,45 @@ module PatientHttp
|
|
|
89
105
|
@handler_mutex = Monitor.new
|
|
90
106
|
@inline_handler = nil
|
|
91
107
|
@default_configuration = nil
|
|
92
|
-
@
|
|
108
|
+
@configuration_provider = nil
|
|
93
109
|
@module_secrets = {}
|
|
94
110
|
@config_mutex = Monitor.new
|
|
95
111
|
|
|
96
112
|
class << self
|
|
97
|
-
#
|
|
113
|
+
# Returns whether the process runs in test mode. The value is `true` when
|
|
114
|
+
# `RAILS_ENV`, `RACK_ENV`, or `APP_ENV` is `test`.
|
|
98
115
|
#
|
|
116
|
+
# @return [Boolean] `true` in test mode.
|
|
99
117
|
# @api private
|
|
100
118
|
def testing?
|
|
101
119
|
@testing
|
|
102
120
|
end
|
|
103
121
|
|
|
104
|
-
#
|
|
122
|
+
# Sets whether the process runs in test mode.
|
|
105
123
|
#
|
|
124
|
+
# @param value [Boolean] `true` to turn on test mode.
|
|
125
|
+
# @return [void]
|
|
106
126
|
# @api private
|
|
107
127
|
def testing=(value)
|
|
108
128
|
@testing = !!value
|
|
109
129
|
end
|
|
110
130
|
|
|
111
|
-
# Registers
|
|
112
|
-
#
|
|
131
|
+
# Registers the request handler. The handler receives every request made
|
|
132
|
+
# with the `PatientHttp` module methods and {RequestHelper}. Job system
|
|
133
|
+
# integration gems register a handler when they load.
|
|
113
134
|
#
|
|
114
|
-
# The handler
|
|
115
|
-
#
|
|
135
|
+
# The handler receives the `request`, `callback`, `callback_args`, and
|
|
136
|
+
# `raise_error_responses` keyword arguments. It should return the request ID.
|
|
116
137
|
#
|
|
117
|
-
# @param callable [#call, nil]
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
# @raise [ArgumentError]
|
|
122
|
-
# @raise [ArgumentError]
|
|
123
|
-
# @
|
|
138
|
+
# @param callable [#call, nil] An object that responds to `call`. Omit it
|
|
139
|
+
# when you give a block.
|
|
140
|
+
# @yield [request:, callback:, callback_args:, raise_error_responses:] The
|
|
141
|
+
# handler. Omit it when you give a callable.
|
|
142
|
+
# @raise [ArgumentError] If you give both a callable and a block, or neither.
|
|
143
|
+
# @raise [ArgumentError] If the callable doesn't respond to `call`.
|
|
144
|
+
# @raise [ArgumentError] If the handler doesn't accept the required keyword
|
|
145
|
+
# arguments.
|
|
146
|
+
# @return [#call] The registered handler.
|
|
124
147
|
def register_handler(callable = nil, &block)
|
|
125
148
|
raise ArgumentError.new("Must provide a callable object or a block") unless callable || block_given?
|
|
126
149
|
raise ArgumentError.new("Cannot provide both a callable object and a block") if callable && block_given?
|
|
@@ -133,19 +156,20 @@ module PatientHttp
|
|
|
133
156
|
@handler_mutex.synchronize { @handler = handler }
|
|
134
157
|
end
|
|
135
158
|
|
|
136
|
-
# Registers
|
|
159
|
+
# Registers the request handler, and raises an error if a handler is
|
|
160
|
+
# already registered. Unlike {.register_handler}, this method can't replace
|
|
161
|
+
# a handler by accident.
|
|
137
162
|
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
# @
|
|
143
|
-
#
|
|
144
|
-
# @raise [
|
|
145
|
-
# @raise [ArgumentError]
|
|
146
|
-
#
|
|
147
|
-
# @
|
|
148
|
-
# @return [#call] the registered handler
|
|
163
|
+
# @param callable [#call, nil] An object that responds to `call`. Omit it
|
|
164
|
+
# when you give a block.
|
|
165
|
+
# @yield [request:, callback:, callback_args:, raise_error_responses:] The
|
|
166
|
+
# handler. Omit it when you give a callable.
|
|
167
|
+
# @raise [RuntimeError] If a handler is already registered.
|
|
168
|
+
# @raise [ArgumentError] If you give both a callable and a block, or neither.
|
|
169
|
+
# @raise [ArgumentError] If the callable doesn't respond to `call`.
|
|
170
|
+
# @raise [ArgumentError] If the handler doesn't accept the required keyword
|
|
171
|
+
# arguments.
|
|
172
|
+
# @return [#call] The registered handler.
|
|
149
173
|
def register_handler!(callable = nil, &block)
|
|
150
174
|
@handler_mutex.synchronize do
|
|
151
175
|
if @handler
|
|
@@ -156,10 +180,10 @@ module PatientHttp
|
|
|
156
180
|
end
|
|
157
181
|
end
|
|
158
182
|
|
|
159
|
-
#
|
|
183
|
+
# Removes the registered request handler.
|
|
160
184
|
#
|
|
161
|
-
# @param handler [#call, nil] If
|
|
162
|
-
# the
|
|
185
|
+
# @param handler [#call, nil] If given, the handler is removed only if it's
|
|
186
|
+
# the registered handler.
|
|
163
187
|
# @return [void]
|
|
164
188
|
def unregister_handler(handler = nil)
|
|
165
189
|
@handler_mutex.synchronize do
|
|
@@ -167,17 +191,16 @@ module PatientHttp
|
|
|
167
191
|
end
|
|
168
192
|
end
|
|
169
193
|
|
|
170
|
-
# Registers a request handler that
|
|
171
|
-
#
|
|
194
|
+
# Registers a request handler that runs each request inline, on the calling
|
|
195
|
+
# thread, instead of sending it to a job system.
|
|
172
196
|
#
|
|
173
|
-
#
|
|
174
|
-
# job
|
|
175
|
-
# {SynchronousExecutor} and the callback
|
|
176
|
-
#
|
|
197
|
+
# Use it in consoles, tests, and development environments that don't load a
|
|
198
|
+
# job system integration gem. Each request runs through
|
|
199
|
+
# {SynchronousExecutor}, and the callback runs before the request method
|
|
200
|
+
# returns.
|
|
177
201
|
#
|
|
178
|
-
# @param config [Configuration, nil] configuration
|
|
179
|
-
#
|
|
180
|
-
# includes any secrets registered with {.register_secret}.
|
|
202
|
+
# @param config [Configuration, nil] The configuration for the requests. If
|
|
203
|
+
# `nil`, {.configuration} applies.
|
|
181
204
|
# @return [void]
|
|
182
205
|
def inline!(config: nil)
|
|
183
206
|
handler = lambda do |request:, callback:, callback_args: nil, raise_error_responses: nil|
|
|
@@ -196,36 +219,37 @@ module PatientHttp
|
|
|
196
219
|
end
|
|
197
220
|
end
|
|
198
221
|
|
|
199
|
-
#
|
|
200
|
-
#
|
|
222
|
+
# Returns whether the registered handler is the inline handler from
|
|
223
|
+
# {.inline!}.
|
|
201
224
|
#
|
|
202
|
-
# @return [Boolean]
|
|
225
|
+
# @return [Boolean] `true` if requests run inline.
|
|
203
226
|
def inline?
|
|
204
227
|
@handler_mutex.synchronize { !@handler.nil? && @handler.equal?(@inline_handler) }
|
|
205
228
|
end
|
|
206
229
|
|
|
207
|
-
#
|
|
230
|
+
# Returns whether a request handler is registered.
|
|
208
231
|
#
|
|
209
|
-
# @return [Boolean]
|
|
232
|
+
# @return [Boolean] `true` if a handler is registered.
|
|
210
233
|
def handler_registered?
|
|
211
234
|
@handler_mutex.synchronize { !@handler.nil? }
|
|
212
235
|
end
|
|
213
236
|
|
|
214
|
-
#
|
|
215
|
-
# {SynchronousExecutor}
|
|
216
|
-
# before
|
|
217
|
-
#
|
|
218
|
-
# @param request [Request]
|
|
219
|
-
# @param callback [Class, String]
|
|
220
|
-
# @param callback_args [Hash, nil] JSON-compatible
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
237
|
+
# Runs a request inline, on the calling thread, through
|
|
238
|
+
# {SynchronousExecutor}. The callback runs with the response or error
|
|
239
|
+
# before this method returns. The registered handler isn't used.
|
|
240
|
+
#
|
|
241
|
+
# @param request [Request] The HTTP request.
|
|
242
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
243
|
+
# @param callback_args [Hash, nil] The JSON-compatible arguments to pass to the
|
|
244
|
+
# callback.
|
|
245
|
+
# @param raise_error_responses [Boolean, nil] If `true`, non-2xx responses go
|
|
246
|
+
# to the `on_error` callback as an {HttpError}. If `nil`, the configuration
|
|
247
|
+
# value applies.
|
|
248
|
+
# @param config [Configuration, nil] The configuration for the request. If
|
|
249
|
+
# `nil`, {.configuration} applies.
|
|
250
|
+
# @return [String] The request ID.
|
|
227
251
|
def execute_inline(request:, callback:, callback_args: nil, raise_error_responses: nil, config: nil)
|
|
228
|
-
config ||=
|
|
252
|
+
config ||= configuration
|
|
229
253
|
raise_error_responses = config.raise_error_responses if raise_error_responses.nil?
|
|
230
254
|
|
|
231
255
|
task = RequestTask.new(
|
|
@@ -242,15 +266,17 @@ module PatientHttp
|
|
|
242
266
|
task.id
|
|
243
267
|
end
|
|
244
268
|
|
|
245
|
-
#
|
|
269
|
+
# Sends a request to the registered request handler.
|
|
246
270
|
#
|
|
247
|
-
# @param request [Request]
|
|
248
|
-
# @param callback [Class, String]
|
|
249
|
-
# @param callback_args [Hash, nil] JSON-compatible
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
271
|
+
# @param request [Request] The HTTP request.
|
|
272
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
273
|
+
# @param callback_args [Hash, nil] The JSON-compatible arguments to pass to the
|
|
274
|
+
# callback.
|
|
275
|
+
# @param raise_error_responses [Boolean, nil] If `true`, non-2xx responses go
|
|
276
|
+
# to the `on_error` callback as an {HttpError}. If `nil`, the configuration
|
|
277
|
+
# value applies.
|
|
278
|
+
# @raise [RuntimeError] If no handler is registered.
|
|
279
|
+
# @return [Object] The value that the request handler returns.
|
|
254
280
|
def execute(request:, callback:, callback_args: nil, raise_error_responses: nil)
|
|
255
281
|
handler = @handler_mutex.synchronize { @handler }
|
|
256
282
|
|
|
@@ -266,100 +292,114 @@ module PatientHttp
|
|
|
266
292
|
)
|
|
267
293
|
end
|
|
268
294
|
|
|
269
|
-
#
|
|
295
|
+
# Makes an async GET request.
|
|
270
296
|
#
|
|
271
|
-
# @param uri [String] absolute URL
|
|
272
|
-
# @param callback [Class, String] callback class
|
|
273
|
-
# @param kwargs [Hash]
|
|
274
|
-
# @return [Object]
|
|
297
|
+
# @param uri [String] The absolute URL.
|
|
298
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
299
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
300
|
+
# @return [Object] The value that the request handler returns.
|
|
275
301
|
def get(uri, callback:, **kwargs)
|
|
276
302
|
request(:get, uri, callback: callback, **kwargs)
|
|
277
303
|
end
|
|
278
304
|
|
|
279
|
-
#
|
|
305
|
+
# Makes an async HEAD request.
|
|
280
306
|
#
|
|
281
|
-
# @param uri [String] absolute URL
|
|
282
|
-
# @param callback [Class, String] callback class
|
|
283
|
-
# @param kwargs [Hash]
|
|
284
|
-
# @return [Object]
|
|
307
|
+
# @param uri [String] The absolute URL.
|
|
308
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
309
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
310
|
+
# @return [Object] The value that the request handler returns.
|
|
285
311
|
def head(uri, callback:, **kwargs)
|
|
286
312
|
request(:head, uri, callback: callback, **kwargs)
|
|
287
313
|
end
|
|
288
314
|
|
|
289
|
-
#
|
|
315
|
+
# Makes an async POST request.
|
|
290
316
|
#
|
|
291
|
-
# @param uri [String] absolute URL
|
|
292
|
-
# @param callback [Class, String] callback class
|
|
293
|
-
# @param kwargs [Hash]
|
|
294
|
-
# @return [Object]
|
|
317
|
+
# @param uri [String] The absolute URL.
|
|
318
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
319
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
320
|
+
# @return [Object] The value that the request handler returns.
|
|
295
321
|
def post(uri, callback:, **kwargs)
|
|
296
322
|
request(:post, uri, callback: callback, **kwargs)
|
|
297
323
|
end
|
|
298
324
|
|
|
299
|
-
#
|
|
325
|
+
# Makes an async PUT request.
|
|
300
326
|
#
|
|
301
|
-
# @param uri [String] absolute URL
|
|
302
|
-
# @param callback [Class, String] callback class
|
|
303
|
-
# @param kwargs [Hash]
|
|
304
|
-
# @return [Object]
|
|
327
|
+
# @param uri [String] The absolute URL.
|
|
328
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
329
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
330
|
+
# @return [Object] The value that the request handler returns.
|
|
305
331
|
def put(uri, callback:, **kwargs)
|
|
306
332
|
request(:put, uri, callback: callback, **kwargs)
|
|
307
333
|
end
|
|
308
334
|
|
|
309
|
-
#
|
|
335
|
+
# Makes an async PATCH request.
|
|
310
336
|
#
|
|
311
|
-
# @param uri [String] absolute URL
|
|
312
|
-
# @param callback [Class, String] callback class
|
|
313
|
-
# @param kwargs [Hash]
|
|
314
|
-
# @return [Object]
|
|
337
|
+
# @param uri [String] The absolute URL.
|
|
338
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
339
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
340
|
+
# @return [Object] The value that the request handler returns.
|
|
315
341
|
def patch(uri, callback:, **kwargs)
|
|
316
342
|
request(:patch, uri, callback: callback, **kwargs)
|
|
317
343
|
end
|
|
318
344
|
|
|
319
|
-
#
|
|
345
|
+
# Makes an async DELETE request.
|
|
320
346
|
#
|
|
321
|
-
# @param uri [String] absolute URL
|
|
322
|
-
# @param callback [Class, String] callback class
|
|
323
|
-
# @param kwargs [Hash]
|
|
324
|
-
# @return [Object]
|
|
347
|
+
# @param uri [String] The absolute URL.
|
|
348
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
349
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
350
|
+
# @return [Object] The value that the request handler returns.
|
|
325
351
|
def delete(uri, callback:, **kwargs)
|
|
326
352
|
request(:delete, uri, callback: callback, **kwargs)
|
|
327
353
|
end
|
|
328
354
|
|
|
329
|
-
#
|
|
355
|
+
# Makes an async QUERY request.
|
|
330
356
|
#
|
|
331
|
-
# @param uri [String] absolute URL
|
|
332
|
-
# @param callback [Class, String] callback class
|
|
333
|
-
# @param kwargs [Hash]
|
|
334
|
-
# @return [Object]
|
|
357
|
+
# @param uri [String] The absolute URL.
|
|
358
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
359
|
+
# @param kwargs [Hash] The request options. See {.request}.
|
|
360
|
+
# @return [Object] The value that the request handler returns.
|
|
335
361
|
def query(uri, callback:, **kwargs)
|
|
336
362
|
request(:query, uri, callback: callback, **kwargs)
|
|
337
363
|
end
|
|
338
364
|
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
# @param
|
|
343
|
-
#
|
|
344
|
-
# @param
|
|
345
|
-
# @param
|
|
346
|
-
# @param
|
|
347
|
-
# @param
|
|
348
|
-
# @param
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
# @param
|
|
352
|
-
# @param
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
# @param
|
|
357
|
-
#
|
|
358
|
-
# @param
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
365
|
+
# Makes an async HTTP request. The request goes to the registered request
|
|
366
|
+
# handler, and this method returns without waiting for the response.
|
|
367
|
+
#
|
|
368
|
+
# @param method [Symbol] The HTTP method: `:get`, `:head`, `:post`, `:put`,
|
|
369
|
+
# `:patch`, `:delete`, or `:query`.
|
|
370
|
+
# @param url [String] The absolute URL.
|
|
371
|
+
# @param callback [Class, String] The callback service class, or its name.
|
|
372
|
+
# @param headers [Hash, nil] The request headers.
|
|
373
|
+
# @param body [String, nil] The request body.
|
|
374
|
+
# @param json [Hash, Array, nil] An object to send as a JSON body. Can't be
|
|
375
|
+
# combined with `body`.
|
|
376
|
+
# @param params [Hash, nil] The query parameters to add to the URL.
|
|
377
|
+
# @param timeout [Numeric, nil] The request timeout in seconds.
|
|
378
|
+
# @param read_timeout [Numeric, nil] The maximum number of seconds to wait for
|
|
379
|
+
# data from the server. The timer covers the wait for the response headers
|
|
380
|
+
# and starts again each time a body chunk arrives. If `nil`, the
|
|
381
|
+
# configuration value applies.
|
|
382
|
+
# @param max_response_size [Integer, nil] The maximum response body size in
|
|
383
|
+
# bytes. If `nil`, the configuration value applies.
|
|
384
|
+
# @param raise_error_responses [Boolean, nil] If `true`, non-2xx responses go
|
|
385
|
+
# to the `on_error` callback as an {HttpError}. If `nil`, the configuration
|
|
386
|
+
# value applies.
|
|
387
|
+
# @param callback_args [Hash, nil] The JSON-compatible arguments to pass to the
|
|
388
|
+
# callback.
|
|
389
|
+
# @param max_redirects [Integer, nil] The maximum number of redirects to
|
|
390
|
+
# follow. If `0`, redirects aren't followed. If `nil`, the configuration
|
|
391
|
+
# value applies.
|
|
392
|
+
# @param follow_method_changing_redirects [Boolean, nil] Whether to follow a
|
|
393
|
+
# redirect that changes the HTTP method. If `nil`, the configuration value
|
|
394
|
+
# applies.
|
|
395
|
+
# @param redirect_strip_headers [String, Array<String>, nil] The names of headers
|
|
396
|
+
# to remove from redirected requests, in addition to the configured names.
|
|
397
|
+
# Names are case insensitive.
|
|
398
|
+
# @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names of
|
|
399
|
+
# the registered preprocessors that run on the request before it's sent.
|
|
400
|
+
# @param processor [String, Symbol, nil] The name of the processor that runs
|
|
401
|
+
# the request. Handlers that support named processors use this value.
|
|
402
|
+
# @return [Object] The value that the request handler returns.
|
|
363
403
|
def request(
|
|
364
404
|
method,
|
|
365
405
|
url,
|
|
@@ -369,6 +409,8 @@ module PatientHttp
|
|
|
369
409
|
json: nil,
|
|
370
410
|
params: nil,
|
|
371
411
|
timeout: nil,
|
|
412
|
+
read_timeout: nil,
|
|
413
|
+
max_response_size: nil,
|
|
372
414
|
raise_error_responses: nil,
|
|
373
415
|
callback_args: nil,
|
|
374
416
|
max_redirects: nil,
|
|
@@ -385,6 +427,8 @@ module PatientHttp
|
|
|
385
427
|
headers: headers,
|
|
386
428
|
params: params,
|
|
387
429
|
timeout: timeout,
|
|
430
|
+
read_timeout: read_timeout,
|
|
431
|
+
max_response_size: max_response_size,
|
|
388
432
|
max_redirects: max_redirects,
|
|
389
433
|
follow_method_changing_redirects: follow_method_changing_redirects,
|
|
390
434
|
redirect_strip_headers: redirect_strip_headers,
|
|
@@ -399,31 +443,31 @@ module PatientHttp
|
|
|
399
443
|
)
|
|
400
444
|
end
|
|
401
445
|
|
|
402
|
-
#
|
|
403
|
-
# parameter value
|
|
446
|
+
# Returns a reference to a named secret. Use the reference as a header or
|
|
447
|
+
# query parameter value.
|
|
404
448
|
#
|
|
405
|
-
# The reference holds only the
|
|
406
|
-
#
|
|
449
|
+
# The reference holds only the name of the secret. The processor resolves
|
|
450
|
+
# the value from the registered secrets when it sends the request, so the
|
|
451
|
+
# value isn't stored in the job queue.
|
|
407
452
|
#
|
|
408
|
-
# @param name [String, Symbol]
|
|
409
|
-
# @return [SecretReference]
|
|
453
|
+
# @param name [String, Symbol] The secret name.
|
|
454
|
+
# @return [SecretReference] The reference to the secret.
|
|
410
455
|
# @see Configuration#register_secret
|
|
411
456
|
def secret(name)
|
|
412
457
|
SecretReference.new(name)
|
|
413
458
|
end
|
|
414
459
|
|
|
415
|
-
#
|
|
460
|
+
# Registers a named secret at the module level.
|
|
416
461
|
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
# register secrets before or after the job-system integration gem configures the
|
|
421
|
-
# processor.
|
|
462
|
+
# The secret is added to {.configuration} now if the configuration exists,
|
|
463
|
+
# or when it's created. As a result, load order doesn't matter. You can
|
|
464
|
+
# register secrets before or after the job system integration gem loads.
|
|
422
465
|
#
|
|
423
|
-
# @param name [String, Symbol]
|
|
424
|
-
# @param value [Object, nil]
|
|
425
|
-
# @yield [name]
|
|
426
|
-
#
|
|
466
|
+
# @param name [String, Symbol] The secret name.
|
|
467
|
+
# @param value [Object, nil] The secret value. Omit it when you give a block.
|
|
468
|
+
# @yield [name] Returns the secret value. The block runs each time the secret
|
|
469
|
+
# is resolved. Omit it when you give a value.
|
|
470
|
+
# @raise [ArgumentError] If you give both a value and a block, or neither.
|
|
427
471
|
# @return [void]
|
|
428
472
|
# @see Configuration#register_secret
|
|
429
473
|
def register_secret(name, value = nil, &block)
|
|
@@ -439,38 +483,144 @@ module PatientHttp
|
|
|
439
483
|
secret_value = block || value
|
|
440
484
|
@module_secrets[name.to_s] = secret_value
|
|
441
485
|
@default_configuration&.register_secret(name, secret_value)
|
|
442
|
-
@inline_configuration&.register_secret(name, secret_value)
|
|
443
486
|
end
|
|
444
487
|
end
|
|
445
488
|
|
|
446
|
-
#
|
|
447
|
-
# {.register_secret} or on the {.
|
|
489
|
+
# Returns whether a secret is registered, at the module level with
|
|
490
|
+
# {.register_secret} or on the {.configuration}.
|
|
448
491
|
#
|
|
449
|
-
# @param name [String, Symbol]
|
|
450
|
-
# @return [Boolean]
|
|
492
|
+
# @param name [String, Symbol] The secret name.
|
|
493
|
+
# @return [Boolean] `true` if the secret is registered.
|
|
451
494
|
def secret_registered?(name)
|
|
495
|
+
return true if @config_mutex.synchronize { @module_secrets.include?(name.to_s) }
|
|
496
|
+
|
|
497
|
+
config = default_configuration
|
|
498
|
+
!config.nil? && config.secret_manager.include?(name)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# Registers the object that builds the configuration for this process.
|
|
502
|
+
#
|
|
503
|
+
# Job system integration gems call this method when they load. Then
|
|
504
|
+
# {.configure} and {.configuration} use the integration's configuration
|
|
505
|
+
# class, which adds the options for that job system. Applications don't
|
|
506
|
+
# call this method.
|
|
507
|
+
#
|
|
508
|
+
# This module stores the configuration, so a process has one configuration
|
|
509
|
+
# object. If a configuration exists when the provider registers, it's
|
|
510
|
+
# discarded, and the provider builds a new one on next use.
|
|
511
|
+
#
|
|
512
|
+
# @param provider [#new_configuration, #configure] The integration module.
|
|
513
|
+
# @raise [ArgumentError] If the provider doesn't respond to
|
|
514
|
+
# `new_configuration` and `configure`.
|
|
515
|
+
# @return [Object] The registered provider.
|
|
516
|
+
# @api private
|
|
517
|
+
def register_configuration_provider(provider)
|
|
518
|
+
unless provider.respond_to?(:new_configuration) && provider.respond_to?(:configure)
|
|
519
|
+
raise ArgumentError.new("A configuration provider must respond to #new_configuration and #configure")
|
|
520
|
+
end
|
|
521
|
+
|
|
452
522
|
@config_mutex.synchronize do
|
|
453
|
-
|
|
523
|
+
previous = @configuration_provider
|
|
524
|
+
|
|
525
|
+
if previous && !previous.equal?(provider)
|
|
526
|
+
warn(
|
|
527
|
+
"PatientHttp: #{provider} is replacing #{previous} as the configuration " \
|
|
528
|
+
"provider. Loading more than one patient_http job-system integration in a process is not " \
|
|
529
|
+
"supported; keep only one of them in your Gemfile."
|
|
530
|
+
)
|
|
531
|
+
end
|
|
454
532
|
|
|
455
|
-
|
|
533
|
+
@configuration_provider = provider
|
|
534
|
+
|
|
535
|
+
# A configuration built before this provider was registered was not built
|
|
536
|
+
# by it, so it does not carry the provider's options. Discard it so the
|
|
537
|
+
# next read builds one through the provider.
|
|
538
|
+
if @default_configuration && !previous.equal?(provider)
|
|
539
|
+
warn(
|
|
540
|
+
"PatientHttp: discarding the configuration that was built before #{provider} was loaded; " \
|
|
541
|
+
"options set on it are lost. Configure PatientHttp after requiring the job-system integration."
|
|
542
|
+
)
|
|
543
|
+
@default_configuration = nil
|
|
544
|
+
end
|
|
456
545
|
end
|
|
546
|
+
|
|
547
|
+
provider
|
|
457
548
|
end
|
|
458
549
|
|
|
459
|
-
#
|
|
460
|
-
# Job-system integration gems should set this at the end of their configure
|
|
461
|
-
# step so that module-level secrets registered with {.register_secret} are
|
|
462
|
-
# applied to the configuration the processor runs with.
|
|
550
|
+
# Returns the registered configuration provider.
|
|
463
551
|
#
|
|
464
|
-
# @return [
|
|
552
|
+
# @return [Object, nil] The provider, or `nil` if no job system integration
|
|
553
|
+
# gem is loaded.
|
|
554
|
+
# @api private
|
|
555
|
+
def configuration_provider
|
|
556
|
+
@config_mutex.synchronize { @configuration_provider }
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Returns the configuration for this process, and creates it on first use.
|
|
560
|
+
#
|
|
561
|
+
# If a job system integration gem is loaded, the configuration is an
|
|
562
|
+
# instance of that integration's configuration class, which adds the
|
|
563
|
+
# options for that job system. Otherwise, it's a {Configuration}. Secrets
|
|
564
|
+
# registered with {.register_secret} are added to it.
|
|
565
|
+
#
|
|
566
|
+
# @return [Configuration] The configuration.
|
|
567
|
+
def configuration
|
|
568
|
+
@config_mutex.synchronize do
|
|
569
|
+
@default_configuration ||= begin
|
|
570
|
+
provider = @configuration_provider
|
|
571
|
+
config = provider ? provider.new_configuration : Configuration.new
|
|
572
|
+
apply_module_secrets(config)
|
|
573
|
+
config
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Yields the configuration to a block.
|
|
579
|
+
#
|
|
580
|
+
# Use this method to configure the gem with any job system. If an
|
|
581
|
+
# integration gem is loaded, the block receives that integration's
|
|
582
|
+
# configuration. Otherwise, it receives a {Configuration}.
|
|
583
|
+
#
|
|
584
|
+
# Every call yields the same configuration object, so options accumulate.
|
|
585
|
+
# Several initializers can each set options without overwriting one
|
|
586
|
+
# another.
|
|
587
|
+
#
|
|
588
|
+
# @example
|
|
589
|
+
# PatientHttp.configure do |config|
|
|
590
|
+
# config.max_connections = 512
|
|
591
|
+
# config.register_secret(:api_token) { ENV["API_TOKEN"] }
|
|
592
|
+
# end
|
|
593
|
+
#
|
|
594
|
+
# @yield [config] The block that sets configuration options.
|
|
595
|
+
# @yieldparam config [Configuration] The configuration.
|
|
596
|
+
# @return [Configuration] The configuration.
|
|
597
|
+
def configure(&block)
|
|
598
|
+
provider = configuration_provider
|
|
599
|
+
return provider.configure(&block) if provider
|
|
600
|
+
|
|
601
|
+
config = configuration
|
|
602
|
+
yield(config) if block
|
|
603
|
+
config
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Returns the configuration if it exists. Unlike {.configuration}, this
|
|
607
|
+
# method doesn't create the configuration.
|
|
608
|
+
#
|
|
609
|
+
# @return [Configuration, nil] The configuration, or `nil` if it doesn't
|
|
610
|
+
# exist yet.
|
|
465
611
|
def default_configuration
|
|
466
612
|
@config_mutex.synchronize { @default_configuration }
|
|
467
613
|
end
|
|
468
614
|
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
615
|
+
# Replaces the configuration. Intended for tests. Applications use
|
|
616
|
+
# {.configure} instead.
|
|
617
|
+
#
|
|
618
|
+
# Secrets registered with {.register_secret} are added to the new
|
|
619
|
+
# configuration. If `nil`, the configuration is discarded, and
|
|
620
|
+
# {.configuration} builds a new one on next use.
|
|
472
621
|
#
|
|
473
|
-
# @param config [Configuration, nil]
|
|
622
|
+
# @param config [Configuration, nil] The configuration, or `nil` to build a
|
|
623
|
+
# new one on next use.
|
|
474
624
|
# @return [void]
|
|
475
625
|
def default_configuration=(config)
|
|
476
626
|
@config_mutex.synchronize do
|
|
@@ -481,28 +631,19 @@ module PatientHttp
|
|
|
481
631
|
|
|
482
632
|
private
|
|
483
633
|
|
|
484
|
-
#
|
|
485
|
-
# or default configuration is available. Module-level secrets are applied to it.
|
|
486
|
-
#
|
|
487
|
-
# @return [Configuration]
|
|
488
|
-
def inline_configuration
|
|
489
|
-
@config_mutex.synchronize do
|
|
490
|
-
@inline_configuration ||= Configuration.new.tap { |config| apply_module_secrets(config) }
|
|
491
|
-
end
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Apply all module-level secrets to the given configuration.
|
|
634
|
+
# Adds the module-level secrets to a configuration.
|
|
495
635
|
#
|
|
496
|
-
# @param config [Configuration]
|
|
636
|
+
# @param config [Configuration] The configuration.
|
|
497
637
|
# @return [void]
|
|
498
638
|
def apply_module_secrets(config)
|
|
499
639
|
@module_secrets.each { |name, value| config.register_secret(name, value) }
|
|
500
640
|
end
|
|
501
641
|
|
|
502
|
-
# Validates that
|
|
642
|
+
# Validates that a handler accepts the required keyword arguments.
|
|
503
643
|
#
|
|
504
|
-
# @param handler [#call]
|
|
505
|
-
# @raise [ArgumentError]
|
|
644
|
+
# @param handler [#call] The handler.
|
|
645
|
+
# @raise [ArgumentError] If the handler doesn't accept the required keyword
|
|
646
|
+
# arguments.
|
|
506
647
|
# @return [void]
|
|
507
648
|
def validate_handler_parameters!(handler)
|
|
508
649
|
required_keywords = %i[request callback callback_args raise_error_responses]
|