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