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