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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c173592c54e9ac9730cbfeca9b81d61a5af9df06839b8abdc2bb2dcb54b228f
|
|
4
|
+
data.tar.gz: 171d9176271d166855129c6c13bb0e234345f3368dbae835a4221e8b28a9deb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0811e744dadbceb89141f2603de7113fec36ab389967fd941fa9202bba3d6c4924e169b594d17b6f03dba0437bb04b5ca5f895515174d2717a316027428a4f85'
|
|
7
|
+
data.tar.gz: 3719152b73fd5994766a26dba869f4f278c2b8362fb79a30b543d123abfcfd996db0a5a2e57daf136f2812674c0644e1ad1671c50c2553a615724d7239312048
|
data/ARCHITECTURE.md
CHANGED
|
@@ -20,13 +20,13 @@ The heart of the system - runs in a dedicated thread with its own Fiber reactor.
|
|
|
20
20
|
Abstract base class that defines the integration point between the pool and your application. Implementations handle completion callbacks, error callbacks, and job retry operations. Concrete implementations (such as those in the `patient_http-sidekiq` or `patient_http-solid_queue` gems) are responsible for using `Configuration#encryptor` to encrypt serialized `Response`/`Error` data before passing it to the job queue. The base class itself does not call encrypt or decrypt; that responsibility belongs to the implementation. This abstraction allows the pool to work with any job system (Sidekiq, SolidQueue, custom queues, etc.).
|
|
21
21
|
|
|
22
22
|
### Request/RequestTemplate
|
|
23
|
-
`Request` is
|
|
23
|
+
`Request` is a value object representing an HTTP request. `RequestTemplate` provides a builder for creating requests with shared configuration (base URL, headers, timeout).
|
|
24
24
|
|
|
25
25
|
### RequestTask
|
|
26
26
|
Wraps a `Request` with execution context: the `TaskHandler`, callback class name, and callback arguments. This is what gets enqueued to the processor.
|
|
27
27
|
|
|
28
28
|
### Response
|
|
29
|
-
|
|
29
|
+
Value object representing an HTTP response. Includes status, headers, body, and callback arguments. Designed to be serializable for passing through job queues.
|
|
30
30
|
|
|
31
31
|
### Error Classes
|
|
32
32
|
Typed error classes (`HttpError`, `RequestError`, `RedirectError`) that are also serializable. Include context about the failed request and callback arguments.
|
|
@@ -44,7 +44,7 @@ Manages processor state transitions (stopped → starting → running → draini
|
|
|
44
44
|
Handles encryption and decryption of serialized payloads at the job queue boundary. Wraps user-provided encryption/decryption callables (which operate on raw bytes) with JSON serialization and Base64 encoding. Instantiated from `Configuration#encryptor`. The `Encryptor` is a helper: concrete `TaskHandler` implementations are responsible for calling `encryptor.encrypt`/`encryptor.decrypt` at every serialization boundary. Encrypted data is enveloped as `{"__encrypted__" => true, "value" => "<base64>"}` to allow transparent no-op pass-through when no encryption is configured.
|
|
45
45
|
|
|
46
46
|
### ExternalStorage/PayloadStore
|
|
47
|
-
Optional external storage for large request/response payloads. Supports file, Redis, S3, and custom adapters.
|
|
47
|
+
Optional external storage for large request/response payloads. Supports file, Redis, S3, ActiveRecord, and custom adapters.
|
|
48
48
|
|
|
49
49
|
## TaskHandler Pattern
|
|
50
50
|
|
|
@@ -110,12 +110,13 @@ Example:
|
|
|
110
110
|
PatientHttp.register_handler do |request:, callback:, callback_args: nil, raise_error_responses: nil|
|
|
111
111
|
task = PatientHttp::RequestTask.new(
|
|
112
112
|
request: request,
|
|
113
|
-
task_handler: MyTaskHandler.new,
|
|
113
|
+
task_handler: MyTaskHandler.new(MyJobSystem.current_job_id),
|
|
114
114
|
callback: callback,
|
|
115
115
|
callback_args: callback_args,
|
|
116
116
|
raise_error_responses: raise_error_responses
|
|
117
117
|
)
|
|
118
118
|
processor.enqueue(task)
|
|
119
|
+
task.id
|
|
119
120
|
end
|
|
120
121
|
|
|
121
122
|
# Use in your application code
|
|
@@ -296,8 +297,8 @@ The processor maintains state through its lifecycle:
|
|
|
296
297
|
|
|
297
298
|
When the processor is stopped with in-flight requests:
|
|
298
299
|
|
|
299
|
-
1. The processor stops accepting new requests (
|
|
300
|
-
2. In-flight requests are given time to complete (
|
|
300
|
+
1. The processor stops accepting new requests (stopping state). Call `drain` first to stop accepting requests before the stop begins.
|
|
301
|
+
2. In-flight requests are given time to complete (`shutdown_timeout`, or the `timeout` passed to `stop`)
|
|
301
302
|
3. Any requests still pending when the timeout expires trigger `TaskHandler#retry`
|
|
302
303
|
4. The application's job system can re-enqueue these requests for later processing
|
|
303
304
|
|
|
@@ -327,7 +328,7 @@ For large request/response payloads, the `ExternalStorage` class provides option
|
|
|
327
328
|
- **Thread-safe queues**: `Thread::Queue` for request enqueueing
|
|
328
329
|
- **Atomic operations**: `Concurrent::AtomicReference` for state
|
|
329
330
|
- **Synchronized access**: Mutexes protect shared data structures
|
|
330
|
-
- **
|
|
331
|
+
- **Value objects**: Request and Response have no setters for their attributes
|
|
331
332
|
|
|
332
333
|
## Further Reading
|
|
333
334
|
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.7.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `PatientHttp.configure` is a single entry point for configuration regardless of which job system is in use. When an integration gem (patient_http-sidekiq, patient_http-solid_queue) is loaded it yields that integration's configuration; otherwise it yields a plain `Configuration` used for inline execution. The same object is yielded on every call, so options accumulate and several initializers can each contribute without overwriting one another. Application code no longer has to name the integration to configure it.
|
|
12
|
+
- `PatientHttp.configuration` returns that configuration, creating it on first use and applying any secrets registered with `PatientHttp.register_secret`. There is no longer a boot order to get right.
|
|
13
|
+
- `PatientHttp.register_configuration_provider` lets an integration gem supply the configuration class that `configure` and `configuration` build. The configuration object itself is stored here, so there is exactly one in a process no matter which module it is reached through. `PatientHttp.configuration_provider` returns the registered provider. Loading two integrations in one process warns instead of silently taking the last one.
|
|
14
|
+
- `Configuration#payload_store_threshold` (default 64KB): the size above which a serialized payload is written to the registered payload store. This moves the option next to `register_payload_store`, where the store it applies to is registered. The integration gems inherit it, and their own `payload_store_threshold` accessors continue to work.
|
|
15
|
+
- A request that fails before any response byte arrives is retried at once when the failure is known to be safe to retry: the server refused the request before processing it (`Protocol::HTTP::RefusedError`, raised for an HTTP/2 GOAWAY, a pooled connection that closed after it was acquired, or a refused stream), or the request method is idempotent (`Request#idempotent?`: GET, HEAD, PUT, DELETE, QUERY) and the connection failed. A connection failure evicts the host's pooled client first, so the retry uses a new connection. A request is retried up to `ImmediateRetries::IMMEDIATE_RETRY_LIMIT` times, or `retries - 1` times when the `retries` setting is higher. A POST or PATCH that fails with `EOFError`, `ECONNRESET`, `ECONNABORTED`, `EPIPE`, or `ETIMEDOUT` before a response is not retried, because the server may have processed it.
|
|
16
|
+
- `Configuration#tcp_keepalive` enables TCP keepalive on pooled connections, as an idle time in seconds or a Hash with `:idle`, `:interval`, and `:count`. Keepalive probes keep NAT and firewall mappings alive while a connection is idle and let the kernel detect a dead peer, so the connection is retired before a request is sent on it.
|
|
17
|
+
- `Configuration#tcp_user_timeout` sets `TCP_USER_TIMEOUT` (Linux only) on pooled connections: the seconds transmitted data may stay unacknowledged before the kernel aborts the connection with `ETIMEDOUT`. A request sent on a connection whose peer has silently gone away fails after this long instead of waiting for `request_timeout`. Acknowledged data is not affected, so a slow response is never cut short. `ETIMEDOUT` from the socket is retried at once for idempotent requests like the other connection failures; the request timeout itself is still never retried.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `PatientHttp.default_configuration` is now the configuration created by `PatientHttp.configuration` rather than a separate slot an integration had to remember to assign. It still returns nil until a configuration exists, and assigning nil discards the configuration so the next read builds a fresh one. Assigning a configuration still applies module level secrets to it.
|
|
22
|
+
- The separate configuration that was lazily created for inline execution is gone. Inline requests run against `PatientHttp.configuration`, so they see the same secrets, payload stores, and preprocessors as queued requests instead of a parallel set.
|
|
23
|
+
- Retries are applied by patient_http alone. The async-http clients make a single attempt per request, so the async-http retries and the immediate retries no longer stack.
|
|
24
|
+
- Requires async-http 0.99 or later and declares protocol-http 0.66 or later as a direct dependency, since its error classes are referenced directly.
|
|
25
|
+
- **`RequestTemplate` no longer defaults to a 30 second timeout.** A template without a `timeout` leaves the request timeout unset, so the configured `request_timeout` applies. The same is true for `request_template` in `RequestHelper`. Templates that need 30 seconds must now set `timeout: 30`.
|
|
26
|
+
- `PatientHttp.execute_inline` and `SynchronousExecutor` make their connections through a `ClientPool` that lives for the one execution, so inline requests honor `connection_timeout`, `protocol`, `tcp_keepalive`, `tcp_user_timeout`, and the immediate retry rules exactly as processor-backed requests do. Previously the connection timeout capped inline response reads and no immediate retries applied inline.
|
|
27
|
+
- Minimum Ruby version is 3.3.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- `RequestTemplate#request` and the `RequestHelper` `async_*` methods accept `max_redirects`, like the `PatientHttp` module methods.
|
|
32
|
+
- `connection_timeout` now bounds only the TCP connect and TLS handshake. It was applied as the socket's IO timeout and so also capped every read and write for the life of the connection, including reads on reused keep-alive connections. A server that paused longer than the connection timeout before or between response bytes failed with `IO::TimeoutError` ("read timeout") even when `request_timeout` allowed more time. Once a connection is established the request timeout alone governs the exchange.
|
|
33
|
+
- `IO::TimeoutError` is classified as a `:timeout` error. It was reported as `:connection` because it inherits from `IOError`.
|
|
34
|
+
- Evicting a host's pooled client no longer stalls the processor. The eviction closed the client while holding the client pool lock, and closing waits for the host's in-flight requests to finish, so one request timing out blocked dispatch of every queued request, to any host, until the slowest in-flight request to that host completed. The client is now removed from the pool immediately and closed in a separate task once its requests finish.
|
|
35
|
+
|
|
7
36
|
## 1.6.1
|
|
8
37
|
|
|
9
38
|
### Fixed
|