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
|
@@ -1,104 +1,159 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PatientHttp
|
|
4
|
-
#
|
|
4
|
+
# The configuration for the processor and its HTTP connections.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# The options include connection limits, timeouts, redirects, secrets,
|
|
7
|
+
# preprocessors, encryption, and payload stores. This class doesn't depend on
|
|
8
|
+
# a job system. Job system integration gems subclass it to add their own
|
|
9
|
+
# options.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# PatientHttp.configure do |config|
|
|
13
|
+
# config.max_connections = 512
|
|
14
|
+
# config.request_timeout = 120
|
|
15
|
+
# end
|
|
9
16
|
class Configuration
|
|
10
|
-
#
|
|
11
|
-
#
|
|
17
|
+
# The salt for key derivation. Changing it makes existing encrypted data
|
|
18
|
+
# unreadable.
|
|
12
19
|
SALT = "patient_http_payload_encryption"
|
|
13
20
|
private_constant :SALT
|
|
14
21
|
|
|
15
|
-
#
|
|
22
|
+
# The default size in bytes above which a serialized payload goes to a
|
|
23
|
+
# payload store instead of the job queue.
|
|
24
|
+
DEFAULT_PAYLOAD_STORE_THRESHOLD = 64 * 1024 # 64KB
|
|
25
|
+
|
|
26
|
+
# @return [Integer] The maximum number of concurrent requests.
|
|
16
27
|
attr_reader :max_connections
|
|
17
28
|
|
|
18
|
-
# @return [Integer, nil]
|
|
29
|
+
# @return [Integer, nil] The maximum number of connections to each host, or
|
|
30
|
+
# `nil` for no limit.
|
|
19
31
|
attr_reader :max_connections_per_host
|
|
20
32
|
|
|
21
|
-
# @return [Integer]
|
|
33
|
+
# @return [Integer] The number of threads that decode responses and deliver
|
|
34
|
+
# results.
|
|
22
35
|
attr_reader :completion_threads
|
|
23
36
|
|
|
24
|
-
# @return [Integer]
|
|
25
|
-
# A retry calls the task handler again, so handlers must be
|
|
37
|
+
# @return [Integer] The number of times to retry result delivery after it
|
|
38
|
+
# fails. A retry calls the task handler again, so handlers must be
|
|
39
|
+
# idempotent.
|
|
26
40
|
attr_reader :completion_retries
|
|
27
41
|
|
|
28
|
-
# @return [Numeric]
|
|
42
|
+
# @return [Numeric] The default request timeout in seconds.
|
|
29
43
|
attr_reader :request_timeout
|
|
30
44
|
|
|
31
|
-
# @return [Numeric]
|
|
45
|
+
# @return [Numeric] The graceful shutdown timeout in seconds.
|
|
32
46
|
attr_reader :shutdown_timeout
|
|
33
47
|
|
|
34
|
-
# @return [Integer]
|
|
48
|
+
# @return [Integer] The maximum response body size in bytes.
|
|
35
49
|
attr_reader :max_response_size
|
|
36
50
|
|
|
37
|
-
# @return [String, nil]
|
|
51
|
+
# @return [String, nil] The default `User-Agent` header value.
|
|
38
52
|
attr_accessor :user_agent
|
|
39
53
|
|
|
40
|
-
# @return [Boolean] Whether
|
|
54
|
+
# @return [Boolean] Whether non-2xx responses go to the `on_error` callback
|
|
55
|
+
# as an {HttpError} by default.
|
|
41
56
|
attr_accessor :raise_error_responses
|
|
42
57
|
|
|
43
|
-
# @return [Integer]
|
|
58
|
+
# @return [Integer] The maximum number of redirects to follow. If `0`,
|
|
59
|
+
# redirects aren't followed.
|
|
44
60
|
attr_reader :max_redirects
|
|
45
61
|
|
|
46
|
-
# @return [Boolean] Whether a redirect that
|
|
47
|
-
#
|
|
48
|
-
#
|
|
62
|
+
# @return [Boolean] Whether to follow a redirect that changes the HTTP
|
|
63
|
+
# method, such as POST to GET on a 302. If `false`, the redirect response
|
|
64
|
+
# is the result.
|
|
49
65
|
attr_reader :follow_method_changing_redirects
|
|
50
66
|
|
|
51
|
-
# @return [Array<String>]
|
|
52
|
-
# redirected requests
|
|
67
|
+
# @return [Array<String>] The lowercase names of headers to remove from all
|
|
68
|
+
# redirected requests.
|
|
53
69
|
attr_reader :redirect_strip_headers
|
|
54
70
|
|
|
55
|
-
# @return [Integer]
|
|
56
|
-
#
|
|
71
|
+
# @return [Integer] The maximum number of hosts whose connections are kept
|
|
72
|
+
# open at the same time.
|
|
57
73
|
attr_reader :connection_pool_size
|
|
58
74
|
|
|
59
|
-
# @return [Numeric, nil]
|
|
75
|
+
# @return [Numeric, nil] The timeout in seconds to open a connection,
|
|
76
|
+
# including the TCP connect and the TLS handshake. It doesn't limit the
|
|
77
|
+
# wait for a response. `request_timeout` sets that limit.
|
|
60
78
|
attr_reader :connection_timeout
|
|
61
79
|
|
|
62
|
-
# @return [
|
|
80
|
+
# @return [Hash, nil] The TCP keepalive settings for each connection, or `nil`
|
|
81
|
+
# if the kernel sends no probes. The hash has the `:idle` seconds before
|
|
82
|
+
# the first probe, the `:interval` seconds between probes, and the `:count`
|
|
83
|
+
# of probes before the connection is closed.
|
|
84
|
+
attr_reader :tcp_keepalive
|
|
85
|
+
|
|
86
|
+
# @return [Numeric, nil] The seconds that sent data can stay unacknowledged
|
|
87
|
+
# before the kernel closes the connection. This value sets
|
|
88
|
+
# `TCP_USER_TIMEOUT`, which is available only on Linux. A request to a peer
|
|
89
|
+
# that stopped without notice then fails without waiting for the request
|
|
90
|
+
# timeout. Acknowledged data isn't affected, so a slow response continues.
|
|
91
|
+
attr_reader :tcp_user_timeout
|
|
92
|
+
|
|
93
|
+
# @return [String, nil] The HTTP or HTTPS proxy URL. The URL can include a
|
|
94
|
+
# user name and password.
|
|
63
95
|
attr_reader :proxy_url
|
|
64
96
|
|
|
65
|
-
# @return [Integer]
|
|
97
|
+
# @return [Integer] The number of retries for failed requests.
|
|
66
98
|
attr_reader :retries
|
|
67
99
|
|
|
68
|
-
# @return [Symbol, nil] HTTP protocol
|
|
69
|
-
# protocol is negotiated with the server
|
|
100
|
+
# @return [Symbol, nil] The HTTP protocol: `:http1` or `:http2`. If `nil`, the
|
|
101
|
+
# protocol is negotiated with the server, and HTTP/2 is preferred for
|
|
102
|
+
# HTTPS.
|
|
70
103
|
attr_reader :protocol
|
|
71
104
|
|
|
72
|
-
# @return [SecretManager] the
|
|
105
|
+
# @return [SecretManager] The manager for the registered secrets.
|
|
73
106
|
attr_reader :secret_manager
|
|
74
107
|
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# @param
|
|
82
|
-
# @param
|
|
83
|
-
# @param
|
|
84
|
-
# @param
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# @param
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# @param
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# @param
|
|
96
|
-
#
|
|
97
|
-
# @param
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
108
|
+
# @return [Integer] The size in bytes above which a serialized payload goes
|
|
109
|
+
# to the registered payload store instead of the job queue.
|
|
110
|
+
attr_reader :payload_store_threshold
|
|
111
|
+
|
|
112
|
+
# Creates a configuration.
|
|
113
|
+
#
|
|
114
|
+
# @param max_connections [Integer] The maximum number of concurrent requests.
|
|
115
|
+
# @param request_timeout [Numeric] The default request timeout in seconds.
|
|
116
|
+
# @param shutdown_timeout [Numeric] The graceful shutdown timeout in seconds.
|
|
117
|
+
# @param logger [Logger, nil] The logger. If `nil`, errors are logged to
|
|
118
|
+
# standard error.
|
|
119
|
+
# @param max_response_size [Integer] The maximum response body size in bytes.
|
|
120
|
+
# @param user_agent [String, nil] The default `User-Agent` header value.
|
|
121
|
+
# @param raise_error_responses [Boolean] Whether non-2xx responses go to the
|
|
122
|
+
# `on_error` callback as an {HttpError} by default.
|
|
123
|
+
# @param max_redirects [Integer] The maximum number of redirects to follow. If
|
|
124
|
+
# `0`, redirects aren't followed.
|
|
125
|
+
# @param follow_method_changing_redirects [Boolean] Whether to follow a
|
|
126
|
+
# redirect that changes the HTTP method, such as POST to GET on a 301, 302,
|
|
127
|
+
# or 303 response. If `false`, the redirect response is the result.
|
|
128
|
+
# @param redirect_strip_headers [String, Array<String>] The names of headers to
|
|
129
|
+
# remove from all redirected requests. Names are case insensitive.
|
|
130
|
+
# @param connection_pool_size [Integer] The maximum number of hosts whose
|
|
131
|
+
# connections are kept open.
|
|
132
|
+
# @param connection_timeout [Numeric, nil] The timeout in seconds to open a
|
|
133
|
+
# connection, including the TCP connect and the TLS handshake.
|
|
134
|
+
# @param tcp_keepalive [Integer, Hash, nil] The idle seconds before the first
|
|
135
|
+
# keepalive probe, or a hash with `:idle`, `:interval`, and `:count`. If
|
|
136
|
+
# `nil`, the kernel sends no probes.
|
|
137
|
+
# @param tcp_user_timeout [Numeric, nil] The seconds that sent data can stay
|
|
138
|
+
# unacknowledged before the kernel closes the connection. Linux only. If
|
|
139
|
+
# `nil`, the kernel default applies.
|
|
140
|
+
# @param proxy_url [String, nil] The HTTP or HTTPS proxy URL.
|
|
141
|
+
# @param retries [Integer] The number of retries for failed requests.
|
|
142
|
+
# @param protocol [Symbol, nil] The HTTP protocol: `:http1` or `:http2`. If
|
|
143
|
+
# `nil`, the protocol is negotiated.
|
|
144
|
+
# @param encryption_key [String, Array<String>, nil] The encryption key, or an
|
|
145
|
+
# array of keys for key rotation. See {#encryption_key=}.
|
|
146
|
+
# @param max_connections_per_host [Integer, nil] The maximum number of
|
|
147
|
+
# connections to each host, or `nil` for no limit.
|
|
148
|
+
# @param completion_threads [Integer] The number of threads that decode
|
|
149
|
+
# responses and deliver results.
|
|
150
|
+
# @param completion_retries [Integer] The number of times to retry result
|
|
151
|
+
# delivery after it fails. A retry calls `TaskHandler#on_complete` or
|
|
152
|
+
# `TaskHandler#on_error` again. If `0`, the first failure is reported
|
|
153
|
+
# without a retry.
|
|
154
|
+
# @param payload_store_threshold [Integer, nil] The size in bytes above which
|
|
155
|
+
# a serialized payload goes to the registered payload store.
|
|
156
|
+
# @raise [ArgumentError] If an option isn't valid.
|
|
102
157
|
def initialize(
|
|
103
158
|
max_connections: 256,
|
|
104
159
|
request_timeout: 60,
|
|
@@ -112,13 +167,16 @@ module PatientHttp
|
|
|
112
167
|
redirect_strip_headers: [],
|
|
113
168
|
connection_pool_size: 100,
|
|
114
169
|
connection_timeout: nil,
|
|
170
|
+
tcp_keepalive: nil,
|
|
171
|
+
tcp_user_timeout: nil,
|
|
115
172
|
proxy_url: nil,
|
|
116
173
|
retries: 3,
|
|
117
174
|
protocol: nil,
|
|
118
175
|
encryption_key: nil,
|
|
119
176
|
max_connections_per_host: nil,
|
|
120
177
|
completion_threads: 2,
|
|
121
|
-
completion_retries: 2
|
|
178
|
+
completion_retries: 2,
|
|
179
|
+
payload_store_threshold: DEFAULT_PAYLOAD_STORE_THRESHOLD
|
|
122
180
|
)
|
|
123
181
|
@mutex = Mutex.new
|
|
124
182
|
|
|
@@ -147,6 +205,8 @@ module PatientHttp
|
|
|
147
205
|
self.redirect_strip_headers = redirect_strip_headers
|
|
148
206
|
self.connection_pool_size = connection_pool_size
|
|
149
207
|
self.connection_timeout = connection_timeout
|
|
208
|
+
self.tcp_keepalive = tcp_keepalive
|
|
209
|
+
self.tcp_user_timeout = tcp_user_timeout
|
|
150
210
|
self.proxy_url = proxy_url
|
|
151
211
|
self.retries = retries
|
|
152
212
|
self.protocol = protocol
|
|
@@ -154,17 +214,28 @@ module PatientHttp
|
|
|
154
214
|
self.max_connections_per_host = max_connections_per_host
|
|
155
215
|
self.completion_threads = completion_threads
|
|
156
216
|
self.completion_retries = completion_retries
|
|
217
|
+
self.payload_store_threshold = payload_store_threshold
|
|
157
218
|
end
|
|
158
219
|
|
|
159
|
-
#
|
|
160
|
-
#
|
|
220
|
+
# @return [Logger] The logger for processor events. The default logger
|
|
221
|
+
# writes errors to standard error.
|
|
161
222
|
attr_accessor :logger
|
|
162
223
|
|
|
224
|
+
# Sets the maximum number of concurrent requests.
|
|
225
|
+
#
|
|
226
|
+
# @param value [Integer] A positive number.
|
|
227
|
+
# @return [void]
|
|
228
|
+
# @raise [ArgumentError] If the value isn't positive.
|
|
163
229
|
def max_connections=(value)
|
|
164
230
|
validate_positive(:max_connections, value)
|
|
165
231
|
@max_connections = value
|
|
166
232
|
end
|
|
167
233
|
|
|
234
|
+
# Sets the maximum number of connections to each host.
|
|
235
|
+
#
|
|
236
|
+
# @param value [Integer, nil] A positive integer, or `nil` for no limit.
|
|
237
|
+
# @return [void]
|
|
238
|
+
# @raise [ArgumentError] If the value isn't `nil` or a positive integer.
|
|
168
239
|
def max_connections_per_host=(value)
|
|
169
240
|
if value.nil?
|
|
170
241
|
@max_connections_per_host = nil
|
|
@@ -175,36 +246,94 @@ module PatientHttp
|
|
|
175
246
|
@max_connections_per_host = value
|
|
176
247
|
end
|
|
177
248
|
|
|
249
|
+
# Sets the number of threads that decode responses and deliver results. If
|
|
250
|
+
# the value is greater than 1, results are delivered concurrently, so task
|
|
251
|
+
# handlers must be thread-safe.
|
|
252
|
+
#
|
|
253
|
+
# @param value [Integer] A positive integer.
|
|
254
|
+
# @return [void]
|
|
255
|
+
# @raise [ArgumentError] If the value isn't a positive integer.
|
|
178
256
|
def completion_threads=(value)
|
|
179
257
|
validate_positive_integer(:completion_threads, value)
|
|
180
258
|
@completion_threads = value
|
|
181
259
|
end
|
|
182
260
|
|
|
261
|
+
# Sets the number of times to retry result delivery after it fails.
|
|
262
|
+
#
|
|
263
|
+
# @param value [Integer] A non-negative integer. If `0`, the first failure is
|
|
264
|
+
# reported without a retry.
|
|
265
|
+
# @return [void]
|
|
266
|
+
# @raise [ArgumentError] If the value isn't a non-negative integer.
|
|
183
267
|
def completion_retries=(value)
|
|
184
268
|
validate_non_negative_integer(:completion_retries, value)
|
|
185
269
|
@completion_retries = value
|
|
186
270
|
end
|
|
187
271
|
|
|
272
|
+
# Sets the size in bytes above which a serialized payload goes to the
|
|
273
|
+
# registered payload store instead of the job queue. This option has an
|
|
274
|
+
# effect only when a payload store is registered with
|
|
275
|
+
# {#register_payload_store}.
|
|
276
|
+
#
|
|
277
|
+
# @param value [Integer, nil] The size in bytes, or `nil` to use the default.
|
|
278
|
+
# @return [void]
|
|
279
|
+
# @raise [ArgumentError] If the value isn't `nil` or a positive integer.
|
|
280
|
+
def payload_store_threshold=(value)
|
|
281
|
+
value = DEFAULT_PAYLOAD_STORE_THRESHOLD if value.nil?
|
|
282
|
+
validate_positive_integer(:payload_store_threshold, value)
|
|
283
|
+
@payload_store_threshold = value
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Sets the default request timeout.
|
|
287
|
+
#
|
|
288
|
+
# @param value [Numeric] A positive number of seconds.
|
|
289
|
+
# @return [void]
|
|
290
|
+
# @raise [ArgumentError] If the value isn't positive.
|
|
188
291
|
def request_timeout=(value)
|
|
189
292
|
validate_positive(:request_timeout, value)
|
|
190
293
|
@request_timeout = value
|
|
191
294
|
end
|
|
192
295
|
|
|
296
|
+
# Sets the graceful shutdown timeout. Keep it less than the stop timeout of
|
|
297
|
+
# the process supervisor, so that in-flight requests finish before a hard
|
|
298
|
+
# kill.
|
|
299
|
+
#
|
|
300
|
+
# @param value [Numeric] A positive number of seconds.
|
|
301
|
+
# @return [void]
|
|
302
|
+
# @raise [ArgumentError] If the value isn't positive.
|
|
193
303
|
def shutdown_timeout=(value)
|
|
194
304
|
validate_positive(:shutdown_timeout, value)
|
|
195
305
|
@shutdown_timeout = value
|
|
196
306
|
end
|
|
197
307
|
|
|
308
|
+
# Sets the maximum response body size. For a compressed response, the limit
|
|
309
|
+
# applies to the decompressed body. A larger response raises
|
|
310
|
+
# {ResponseTooLargeError}.
|
|
311
|
+
#
|
|
312
|
+
# @param value [Integer] A positive number of bytes.
|
|
313
|
+
# @return [void]
|
|
314
|
+
# @raise [ArgumentError] If the value isn't positive.
|
|
198
315
|
def max_response_size=(value)
|
|
199
316
|
validate_positive(:max_response_size, value)
|
|
200
317
|
@max_response_size = value
|
|
201
318
|
end
|
|
202
319
|
|
|
320
|
+
# Sets the maximum number of redirects to follow.
|
|
321
|
+
#
|
|
322
|
+
# @param value [Integer] A non-negative integer. If `0`, redirects aren't
|
|
323
|
+
# followed.
|
|
324
|
+
# @return [void]
|
|
325
|
+
# @raise [ArgumentError] If the value isn't a non-negative integer.
|
|
203
326
|
def max_redirects=(value)
|
|
204
327
|
validate_non_negative_integer(:max_redirects, value)
|
|
205
328
|
@max_redirects = value
|
|
206
329
|
end
|
|
207
330
|
|
|
331
|
+
# Sets whether to follow a redirect that changes the HTTP method, such as
|
|
332
|
+
# POST to GET on a 302.
|
|
333
|
+
#
|
|
334
|
+
# @param value [Boolean] If `false`, the redirect response is the result.
|
|
335
|
+
# @return [void]
|
|
336
|
+
# @raise [ArgumentError] If the value isn't `true` or `false`.
|
|
208
337
|
def follow_method_changing_redirects=(value)
|
|
209
338
|
unless value == true || value == false
|
|
210
339
|
raise ArgumentError.new("follow_method_changing_redirects must be true or false, got: #{value.inspect}")
|
|
@@ -213,15 +342,34 @@ module PatientHttp
|
|
|
213
342
|
@follow_method_changing_redirects = value
|
|
214
343
|
end
|
|
215
344
|
|
|
345
|
+
# Sets the names of headers to remove from all redirected requests. The
|
|
346
|
+
# `Authorization` and `Cookie` headers are always removed on cross-origin
|
|
347
|
+
# redirects.
|
|
348
|
+
#
|
|
349
|
+
# @param value [String, Array<String>, nil] The header names. Names are case
|
|
350
|
+
# insensitive.
|
|
351
|
+
# @return [void]
|
|
216
352
|
def redirect_strip_headers=(value)
|
|
217
353
|
@redirect_strip_headers = RedirectHelper.normalize_header_names(value)
|
|
218
354
|
end
|
|
219
355
|
|
|
356
|
+
# Sets the maximum number of hosts whose connections are kept open.
|
|
357
|
+
#
|
|
358
|
+
# @param value [Integer] A positive integer.
|
|
359
|
+
# @return [void]
|
|
360
|
+
# @raise [ArgumentError] If the value isn't a positive integer.
|
|
220
361
|
def connection_pool_size=(value)
|
|
221
362
|
validate_positive_integer(:connection_pool_size, value)
|
|
222
363
|
@connection_pool_size = value
|
|
223
364
|
end
|
|
224
365
|
|
|
366
|
+
# Sets the timeout to open a connection, including the TCP connect and the
|
|
367
|
+
# TLS handshake. It doesn't limit the wait for a response.
|
|
368
|
+
#
|
|
369
|
+
# @param value [Numeric, nil] A positive number of seconds, or `nil` for no
|
|
370
|
+
# limit.
|
|
371
|
+
# @return [void]
|
|
372
|
+
# @raise [ArgumentError] If the value isn't `nil` or positive.
|
|
225
373
|
def connection_timeout=(value)
|
|
226
374
|
if value.nil?
|
|
227
375
|
@connection_timeout = nil
|
|
@@ -232,6 +380,60 @@ module PatientHttp
|
|
|
232
380
|
@connection_timeout = value
|
|
233
381
|
end
|
|
234
382
|
|
|
383
|
+
# Sets TCP keepalive for pooled connections. The kernel sends probes on an
|
|
384
|
+
# idle connection, which keeps NAT and firewall mappings open and finds dead
|
|
385
|
+
# peers.
|
|
386
|
+
#
|
|
387
|
+
# @param value [Integer, Hash, nil] The idle seconds before the first probe,
|
|
388
|
+
# or a hash with `:idle` and the optional `:interval` (default 10 seconds)
|
|
389
|
+
# and `:count` (default 3 probes). If `nil`, the kernel sends no probes.
|
|
390
|
+
# @return [void]
|
|
391
|
+
# @raise [ArgumentError] If the hash has an unknown key, or a value isn't a
|
|
392
|
+
# positive integer.
|
|
393
|
+
def tcp_keepalive=(value)
|
|
394
|
+
if value.nil?
|
|
395
|
+
@tcp_keepalive = nil
|
|
396
|
+
return
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
settings = value.is_a?(Hash) ? value.transform_keys(&:to_sym) : {idle: value}
|
|
400
|
+
unknown = settings.keys - [:idle, :interval, :count]
|
|
401
|
+
unless unknown.empty?
|
|
402
|
+
raise ArgumentError.new("tcp_keepalive has unknown keys: #{unknown.inspect}")
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
settings = {interval: 10, count: 3}.merge(settings)
|
|
406
|
+
validate_positive_integer(:tcp_keepalive_idle, settings[:idle])
|
|
407
|
+
validate_positive_integer(:tcp_keepalive_interval, settings[:interval])
|
|
408
|
+
validate_positive_integer(:tcp_keepalive_count, settings[:count])
|
|
409
|
+
@tcp_keepalive = settings.slice(:idle, :interval, :count).freeze
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Sets the seconds that sent data can stay unacknowledged before the kernel
|
|
413
|
+
# closes the connection. The value applies only on platforms that support
|
|
414
|
+
# `TCP_USER_TIMEOUT`, which is Linux.
|
|
415
|
+
#
|
|
416
|
+
# @param value [Numeric, nil] A positive number of seconds, or `nil` to use
|
|
417
|
+
# the kernel default.
|
|
418
|
+
# @return [void]
|
|
419
|
+
# @raise [ArgumentError] If the value isn't `nil` or positive.
|
|
420
|
+
def tcp_user_timeout=(value)
|
|
421
|
+
if value.nil?
|
|
422
|
+
@tcp_user_timeout = nil
|
|
423
|
+
return
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
validate_positive(:tcp_user_timeout, value)
|
|
427
|
+
@tcp_user_timeout = value
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Sets the HTTP or HTTPS proxy URL.
|
|
431
|
+
#
|
|
432
|
+
# @param value [String, nil] The proxy URL, or `nil` for no proxy. The URL
|
|
433
|
+
# can include a user name and password, for example
|
|
434
|
+
# `http://user:pass@proxy.example.com:8080`.
|
|
435
|
+
# @return [void]
|
|
436
|
+
# @raise [ArgumentError] If the value isn't a valid HTTP or HTTPS URL.
|
|
235
437
|
def proxy_url=(value)
|
|
236
438
|
if value.nil?
|
|
237
439
|
@proxy_url = nil
|
|
@@ -242,11 +444,24 @@ module PatientHttp
|
|
|
242
444
|
@proxy_url = value
|
|
243
445
|
end
|
|
244
446
|
|
|
447
|
+
# Sets the number of retries for failed requests.
|
|
448
|
+
#
|
|
449
|
+
# @param value [Integer] A non-negative integer.
|
|
450
|
+
# @return [void]
|
|
451
|
+
# @raise [ArgumentError] If the value isn't a non-negative integer.
|
|
245
452
|
def retries=(value)
|
|
246
453
|
validate_non_negative_integer(:retries, value)
|
|
247
454
|
@retries = value
|
|
248
455
|
end
|
|
249
456
|
|
|
457
|
+
# Sets the HTTP protocol. The `:http1` value also limits the TLS ALPN
|
|
458
|
+
# advertisement to `http/1.1`, which can work around proxies that intercept
|
|
459
|
+
# SSL and don't handle HTTP/2 correctly.
|
|
460
|
+
#
|
|
461
|
+
# @param value [Symbol, String, nil] `:http1` or `:http2`, or `nil` to
|
|
462
|
+
# negotiate the protocol with the server.
|
|
463
|
+
# @return [void]
|
|
464
|
+
# @raise [ArgumentError] If the value isn't a supported protocol.
|
|
250
465
|
def protocol=(value)
|
|
251
466
|
if value.nil?
|
|
252
467
|
@protocol = nil
|
|
@@ -261,26 +476,47 @@ module PatientHttp
|
|
|
261
476
|
@protocol = value
|
|
262
477
|
end
|
|
263
478
|
|
|
264
|
-
#
|
|
479
|
+
# Sets the callable that encrypts serialized payloads. Set {#decryption} as
|
|
480
|
+
# well.
|
|
265
481
|
#
|
|
266
|
-
# @param callable [#call, nil] An object that
|
|
267
|
-
#
|
|
268
|
-
# @
|
|
482
|
+
# @param callable [#call, nil] An object that takes the bytes as a String and
|
|
483
|
+
# returns the encrypted bytes. Omit it when you give a block.
|
|
484
|
+
# @yield [data] Returns the encrypted bytes. Omit it when you give a
|
|
485
|
+
# callable.
|
|
486
|
+
# @yieldparam data [String] The bytes to encrypt.
|
|
487
|
+
# @return [void]
|
|
488
|
+
# @raise [ArgumentError] If you give both a callable and a block, or if the
|
|
489
|
+
# callable doesn't respond to `call`.
|
|
269
490
|
def encryption(callable = nil, &block)
|
|
270
491
|
@encryption = resolve_callable(:encryption, callable, &block)
|
|
271
492
|
@encryptor = nil
|
|
272
493
|
end
|
|
273
494
|
|
|
274
|
-
#
|
|
495
|
+
# Sets the callable that decrypts serialized payloads. Set {#encryption} as
|
|
496
|
+
# well.
|
|
275
497
|
#
|
|
276
|
-
# @param callable [#call, nil] An object that
|
|
277
|
-
#
|
|
278
|
-
# @
|
|
498
|
+
# @param callable [#call, nil] An object that takes the encrypted bytes as a
|
|
499
|
+
# String and returns the decrypted bytes. Omit it when you give a block.
|
|
500
|
+
# @yield [data] Returns the decrypted bytes. Omit it when you give a
|
|
501
|
+
# callable.
|
|
502
|
+
# @yieldparam data [String] The bytes to decrypt.
|
|
503
|
+
# @return [void]
|
|
504
|
+
# @raise [ArgumentError] If you give both a callable and a block, or if the
|
|
505
|
+
# callable doesn't respond to `call`.
|
|
279
506
|
def decryption(callable = nil, &block)
|
|
280
507
|
@decryption = resolve_callable(:decryption, callable, &block)
|
|
281
508
|
@encryptor = nil
|
|
282
509
|
end
|
|
283
510
|
|
|
511
|
+
# Sets the encryption key. Payloads are encrypted with
|
|
512
|
+
# `ActiveSupport::MessageEncryptor` and AES-256-GCM. This method sets
|
|
513
|
+
# {#encryption} and {#decryption}.
|
|
514
|
+
#
|
|
515
|
+
# @param keys [String, Array<String>, nil] The key, or an array of keys for
|
|
516
|
+
# key rotation. The first key encrypts data, and all keys are tried for
|
|
517
|
+
# decryption. If `nil` or empty, encryption is turned off.
|
|
518
|
+
# @return [void]
|
|
519
|
+
# @raise [ArgumentError] If Active Support isn't available.
|
|
284
520
|
def encryption_key=(keys)
|
|
285
521
|
keys = Array(keys).map(&:to_s).reject(&:empty?)
|
|
286
522
|
if keys.empty?
|
|
@@ -312,25 +548,25 @@ module PatientHttp
|
|
|
312
548
|
@encryptor = nil
|
|
313
549
|
end
|
|
314
550
|
|
|
315
|
-
#
|
|
316
|
-
#
|
|
551
|
+
# Returns the encryptor for serialized payloads. If encryption isn't set,
|
|
552
|
+
# the encryptor returns data unchanged.
|
|
317
553
|
#
|
|
318
|
-
# @return [Encryptor]
|
|
554
|
+
# @return [Encryptor] The encryptor.
|
|
319
555
|
def encryptor
|
|
320
556
|
@encryptor ||= Encryptor.new(encryption: @encryption, decryption: @decryption)
|
|
321
557
|
end
|
|
322
558
|
|
|
323
|
-
#
|
|
324
|
-
#
|
|
559
|
+
# Registers a named secret. Requests refer to the secret with
|
|
560
|
+
# {PatientHttp.secret}, so the value isn't stored in the job queue.
|
|
325
561
|
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
562
|
+
# Give the value directly or as a block. The block runs with the secret
|
|
563
|
+
# name each time the secret is resolved. Use a block to read a value when
|
|
564
|
+
# it's needed, for example from the environment.
|
|
329
565
|
#
|
|
330
|
-
# @param name [String, Symbol]
|
|
331
|
-
# @param value [Object, nil]
|
|
332
|
-
# @yield [name]
|
|
333
|
-
# @raise [ArgumentError]
|
|
566
|
+
# @param name [String, Symbol] The secret name.
|
|
567
|
+
# @param value [Object, nil] The secret value. Omit it when you give a block.
|
|
568
|
+
# @yield [name] Returns the secret value. Omit it when you give a value.
|
|
569
|
+
# @raise [ArgumentError] If you give both a value and a block, or neither.
|
|
334
570
|
# @return [void]
|
|
335
571
|
def register_secret(name, value = nil, &block)
|
|
336
572
|
if value.nil? && block.nil?
|
|
@@ -347,23 +583,25 @@ module PatientHttp
|
|
|
347
583
|
end
|
|
348
584
|
end
|
|
349
585
|
|
|
350
|
-
#
|
|
351
|
-
#
|
|
586
|
+
# Registers a named preprocessor. A preprocessor changes a request
|
|
587
|
+
# immediately before it's sent, for example to sign it.
|
|
352
588
|
#
|
|
353
|
-
# The preprocessor
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
#
|
|
357
|
-
# change the request headers and append query parameters.
|
|
589
|
+
# The preprocessor receives an {OutgoingRequest}. At that time, secret
|
|
590
|
+
# references are resolved, and the `x-request-id` and default `user-agent`
|
|
591
|
+
# headers are set. The preprocessor can change the headers and add query
|
|
592
|
+
# parameters.
|
|
358
593
|
#
|
|
359
|
-
# Requests
|
|
360
|
-
# credentials it uses
|
|
594
|
+
# Requests refer to a preprocessor by name. The callable, and any
|
|
595
|
+
# credentials that it uses, stay in the processor and aren't serialized.
|
|
361
596
|
#
|
|
362
|
-
# @param name [String, Symbol]
|
|
363
|
-
# @param callable [#call, nil] object
|
|
364
|
-
#
|
|
365
|
-
# @
|
|
366
|
-
#
|
|
597
|
+
# @param name [String, Symbol] The preprocessor name.
|
|
598
|
+
# @param callable [#call, nil] An object that takes the outgoing request.
|
|
599
|
+
# Omit it when you give a block.
|
|
600
|
+
# @yield [outgoing_request] Changes the outgoing request. Omit it when you
|
|
601
|
+
# give a callable.
|
|
602
|
+
# @yieldparam outgoing_request [OutgoingRequest] The request to change.
|
|
603
|
+
# @raise [ArgumentError] If you give both a callable and a block, or neither,
|
|
604
|
+
# or if the preprocessor doesn't take exactly one argument.
|
|
367
605
|
# @return [void]
|
|
368
606
|
def register_preprocessor(name, callable = nil, &block)
|
|
369
607
|
preprocessor = resolve_callable(:preprocessor, callable, &block)
|
|
@@ -376,28 +614,30 @@ module PatientHttp
|
|
|
376
614
|
end
|
|
377
615
|
end
|
|
378
616
|
|
|
379
|
-
#
|
|
617
|
+
# Returns a registered preprocessor.
|
|
380
618
|
#
|
|
381
|
-
# @param name [String, Symbol]
|
|
382
|
-
# @return [#call, nil]
|
|
619
|
+
# @param name [String, Symbol] The preprocessor name.
|
|
620
|
+
# @return [#call, nil] The preprocessor, or `nil` if it isn't registered.
|
|
383
621
|
def preprocessor(name)
|
|
384
622
|
@preprocessors[name.to_s]
|
|
385
623
|
end
|
|
386
624
|
|
|
387
|
-
#
|
|
625
|
+
# Registers a payload store for large payloads. A serialized payload larger
|
|
626
|
+
# than {#payload_store_threshold} goes to the store instead of the job
|
|
627
|
+
# queue.
|
|
388
628
|
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
629
|
+
# References to the stored data include the store name. If you change the
|
|
630
|
+
# name, existing references become invalid.
|
|
391
631
|
#
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# to other registered stores remain valid for reading.
|
|
632
|
+
# To move to a new store, register both. The last store registered is used
|
|
633
|
+
# for new writes. The other stores remain available for reads.
|
|
395
634
|
#
|
|
396
|
-
# @param name [Symbol, String]
|
|
397
|
-
# @param adapter [Symbol, String] The adapter
|
|
398
|
-
#
|
|
635
|
+
# @param name [Symbol, String] The unique name for the store.
|
|
636
|
+
# @param adapter [Symbol, String] The adapter: `:file`, `:redis`, `:s3`,
|
|
637
|
+
# `:active_record`, or the name of a custom adapter.
|
|
638
|
+
# @param options [Hash] The options for the adapter.
|
|
399
639
|
# @return [void]
|
|
400
|
-
# @raise [ArgumentError] If the adapter
|
|
640
|
+
# @raise [ArgumentError] If the adapter isn't registered.
|
|
401
641
|
def register_payload_store(name, adapter:, **options)
|
|
402
642
|
name = name.to_sym
|
|
403
643
|
adapter = adapter.to_sym
|
|
@@ -418,10 +658,12 @@ module PatientHttp
|
|
|
418
658
|
end
|
|
419
659
|
end
|
|
420
660
|
|
|
421
|
-
#
|
|
661
|
+
# Returns a registered payload store.
|
|
422
662
|
#
|
|
423
|
-
# @param name [Symbol, String, nil]
|
|
424
|
-
#
|
|
663
|
+
# @param name [Symbol, String, nil] The store name. If `nil`, the default store
|
|
664
|
+
# is returned.
|
|
665
|
+
# @return [PayloadStore::Base, nil] The store, or `nil` if it isn't
|
|
666
|
+
# registered.
|
|
425
667
|
def payload_store(name = nil)
|
|
426
668
|
if name.nil?
|
|
427
669
|
return nil unless @default_payload_store_name
|
|
@@ -432,20 +674,23 @@ module PatientHttp
|
|
|
432
674
|
end
|
|
433
675
|
end
|
|
434
676
|
|
|
435
|
-
#
|
|
677
|
+
# Returns the name of the default payload store, which is the store for new
|
|
678
|
+
# writes.
|
|
436
679
|
#
|
|
437
|
-
# @return [Symbol, nil] The
|
|
680
|
+
# @return [Symbol, nil] The store name, or `nil` if no store is registered.
|
|
438
681
|
attr_reader :default_payload_store_name
|
|
439
682
|
|
|
440
|
-
#
|
|
683
|
+
# Returns all registered payload stores.
|
|
441
684
|
#
|
|
442
|
-
# @return [Hash{Symbol => PayloadStore::Base}]
|
|
685
|
+
# @return [Hash{Symbol => PayloadStore::Base}] A copy of the stores, keyed by
|
|
686
|
+
# name.
|
|
443
687
|
def payload_stores
|
|
444
688
|
@payload_stores.dup
|
|
445
689
|
end
|
|
446
690
|
|
|
447
|
-
#
|
|
448
|
-
#
|
|
691
|
+
# Returns the configuration as a hash for inspection.
|
|
692
|
+
#
|
|
693
|
+
# @return [Hash{String => Object}] The option values, keyed by option name.
|
|
449
694
|
def to_h
|
|
450
695
|
{
|
|
451
696
|
"max_connections" => max_connections,
|
|
@@ -460,6 +705,8 @@ module PatientHttp
|
|
|
460
705
|
"redirect_strip_headers" => redirect_strip_headers,
|
|
461
706
|
"connection_pool_size" => connection_pool_size,
|
|
462
707
|
"connection_timeout" => connection_timeout,
|
|
708
|
+
"tcp_keepalive" => tcp_keepalive,
|
|
709
|
+
"tcp_user_timeout" => tcp_user_timeout,
|
|
463
710
|
"proxy_url" => proxy_url,
|
|
464
711
|
"retries" => retries,
|
|
465
712
|
"protocol" => protocol,
|
|
@@ -468,6 +715,7 @@ module PatientHttp
|
|
|
468
715
|
"completion_retries" => completion_retries,
|
|
469
716
|
"payload_stores" => payload_stores.keys,
|
|
470
717
|
"default_payload_store" => default_payload_store_name,
|
|
718
|
+
"payload_store_threshold" => payload_store_threshold,
|
|
471
719
|
"secrets" => @mutex.synchronize { @secrets.keys },
|
|
472
720
|
"preprocessors" => @mutex.synchronize { @preprocessors.keys }
|
|
473
721
|
}
|
|
@@ -487,7 +735,7 @@ module PatientHttp
|
|
|
487
735
|
callable || block
|
|
488
736
|
end
|
|
489
737
|
|
|
490
|
-
#
|
|
738
|
+
# Validates that a preprocessor can be called with one positional argument.
|
|
491
739
|
def validate_preprocessor_parameters!(preprocessor)
|
|
492
740
|
method_obj = preprocessor.is_a?(Proc) ? preprocessor : preprocessor.method(:call)
|
|
493
741
|
parameters = method_obj.parameters
|
|
@@ -528,9 +776,9 @@ module PatientHttp
|
|
|
528
776
|
raise ArgumentError.new("#{attribute} must be a valid URL, got: #{value.inspect}")
|
|
529
777
|
end
|
|
530
778
|
|
|
531
|
-
#
|
|
779
|
+
# Loads the class of a built-in adapter.
|
|
532
780
|
#
|
|
533
|
-
# @param adapter [Symbol] The adapter name
|
|
781
|
+
# @param adapter [Symbol] The adapter name.
|
|
534
782
|
# @return [void]
|
|
535
783
|
def ensure_adapter_loaded(adapter)
|
|
536
784
|
case adapter
|