patient_http 1.0.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 +7 -0
- data/ARCHITECTURE.md +322 -0
- data/CHANGELOG.md +30 -0
- data/MIT-LICENSE +20 -0
- data/README.md +653 -0
- data/VERSION +1 -0
- data/db/migrate/20250101000000_create_patient_http_payloads.rb +15 -0
- data/lib/patient_http/callback_args.rb +176 -0
- data/lib/patient_http/callback_validator.rb +52 -0
- data/lib/patient_http/class_helper.rb +26 -0
- data/lib/patient_http/client.rb +80 -0
- data/lib/patient_http/client_pool.rb +178 -0
- data/lib/patient_http/configuration.rb +365 -0
- data/lib/patient_http/encryptor.rb +69 -0
- data/lib/patient_http/error.rb +76 -0
- data/lib/patient_http/external_storage.rb +134 -0
- data/lib/patient_http/http_error.rb +106 -0
- data/lib/patient_http/http_headers.rb +99 -0
- data/lib/patient_http/lifecycle_manager.rb +174 -0
- data/lib/patient_http/payload.rb +160 -0
- data/lib/patient_http/payload_store/active_record_store.rb +102 -0
- data/lib/patient_http/payload_store/base.rb +150 -0
- data/lib/patient_http/payload_store/file_store.rb +92 -0
- data/lib/patient_http/payload_store/redis_store.rb +98 -0
- data/lib/patient_http/payload_store/s3_store.rb +94 -0
- data/lib/patient_http/payload_store.rb +11 -0
- data/lib/patient_http/processor.rb +538 -0
- data/lib/patient_http/processor_observer.rb +48 -0
- data/lib/patient_http/rails/engine.rb +21 -0
- data/lib/patient_http/redirect_error.rb +136 -0
- data/lib/patient_http/redirect_helper.rb +90 -0
- data/lib/patient_http/request.rb +158 -0
- data/lib/patient_http/request_error.rb +150 -0
- data/lib/patient_http/request_helper.rb +230 -0
- data/lib/patient_http/request_task.rb +308 -0
- data/lib/patient_http/request_template.rb +114 -0
- data/lib/patient_http/response.rb +183 -0
- data/lib/patient_http/response_reader.rb +135 -0
- data/lib/patient_http/synchronous_executor.rb +241 -0
- data/lib/patient_http/task_handler.rb +55 -0
- data/lib/patient_http/time_helper.rb +32 -0
- data/lib/patient_http.rb +313 -0
- data/patient_http.gemspec +48 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c5cda06fbc0db9bc2beb2264a718afb1dedc8c2b27fee4f8e8bd9c417086a6b2
|
|
4
|
+
data.tar.gz: edaa46086b479884d618fc144aeb5bd231196424df7061c71fa242c61de4a564
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 617a2dbbdb9df31eb19f9b48d852cfb15f1c43ed26e4d552f0674fcced748c88b90cbbc7d47e7b8e2a0b7b88b8b4285c3cbca5dd09fec6fbee91e6e7b050378c
|
|
7
|
+
data.tar.gz: e8ea8de4be59c91cb686b1e4249d57a5c4dc50bc1ccf94e6c0dfd8a091d9b3ceb94544eb580924578d845b074d7a478fddce8a1e694706a814e62bd3d03fd604
|
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
PatientHttp provides a mechanism to offload HTTP requests from application threads to a dedicated async I/O processor. The gem uses Ruby's Fiber-based concurrency to handle hundreds of concurrent HTTP requests without blocking application threads.
|
|
6
|
+
|
|
7
|
+
## Key Design Principles
|
|
8
|
+
|
|
9
|
+
1. **Non-blocking Threads**: Application threads enqueue HTTP requests and immediately return, freeing them to do other work
|
|
10
|
+
2. **Fiber-based Concurrency**: A dedicated processor thread uses the `async` gem to multiplex hundreds of concurrent HTTP connections
|
|
11
|
+
3. **Callback Pattern**: HTTP responses are delivered via a pluggable `TaskHandler` that integrates with any job system
|
|
12
|
+
4. **Serializable Objects**: Response and error objects are designed to be serialized and passed through job queues
|
|
13
|
+
|
|
14
|
+
## Core Components
|
|
15
|
+
|
|
16
|
+
### Processor
|
|
17
|
+
The heart of the system - runs in a dedicated thread with its own Fiber reactor. Manages the async HTTP request queue and handles concurrent request execution using the `async` gem.
|
|
18
|
+
|
|
19
|
+
### TaskHandler
|
|
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
|
+
|
|
22
|
+
### Request/RequestTemplate
|
|
23
|
+
`Request` is an immutable value object representing an HTTP request. `RequestTemplate` provides a builder for creating requests with shared configuration (base URL, headers, timeout).
|
|
24
|
+
|
|
25
|
+
### RequestTask
|
|
26
|
+
Wraps a `Request` with execution context: the `TaskHandler`, callback class name, and callback arguments. This is what gets enqueued to the processor.
|
|
27
|
+
|
|
28
|
+
### Response
|
|
29
|
+
Immutable value object representing an HTTP response. Includes status, headers, body, and callback arguments. Designed to be serializable for passing through job queues.
|
|
30
|
+
|
|
31
|
+
### Error Classes
|
|
32
|
+
Typed error classes (`HttpError`, `RequestError`, `RedirectError`) that are also serializable. Include context about the failed request and callback arguments.
|
|
33
|
+
|
|
34
|
+
### RequestHelper
|
|
35
|
+
A mixin module that provides a simplified interface for making async HTTP requests. Allows applications to use the same request interface while swapping out the underlying queueing mechanism. By registering a custom handler, applications can integrate with any job queue system (Sidekiq, Solid Queue, etc.) without changing request-making code. This decouples the request interface from the async processing infrastructure.
|
|
36
|
+
|
|
37
|
+
### Client/ClientPool
|
|
38
|
+
Internal HTTP client that handles connection pooling, HTTP/2 support, and request execution within the Fiber reactor.
|
|
39
|
+
|
|
40
|
+
### LifecycleManager
|
|
41
|
+
Manages processor state transitions (stopped → starting → running → draining → stopping) with thread-safe state machines.
|
|
42
|
+
|
|
43
|
+
### Encryptor
|
|
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
|
+
|
|
46
|
+
### ExternalStorage/PayloadStore
|
|
47
|
+
Optional external storage for large request/response payloads. Supports file, Redis, S3, and custom adapters.
|
|
48
|
+
|
|
49
|
+
## TaskHandler Pattern
|
|
50
|
+
|
|
51
|
+
The `TaskHandler` abstract class defines how the processor communicates results back to your application:
|
|
52
|
+
|
|
53
|
+
- **on_complete(response, callback)**: Called when an HTTP request succeeds. Your implementation should enqueue the response for processing (e.g., via a background job).
|
|
54
|
+
- **on_error(error, callback)**: Called when an HTTP request fails. Your implementation should enqueue the error for handling.
|
|
55
|
+
- **retry**: Called when the processor shuts down with in-flight requests. Your implementation should re-enqueue the original job.
|
|
56
|
+
|
|
57
|
+
> **Important:** TaskHandler callbacks run on the processor's reactor thread. They should be lightweight and fast -- typically just enqueuing a message for another system to pick up. Doing heavy processing in a callback will block the reactor and delay other in-flight requests.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
```ruby
|
|
61
|
+
class MyTaskHandler < PatientHttp::TaskHandler
|
|
62
|
+
def initialize(job_id)
|
|
63
|
+
@job_id = job_id
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def on_complete(response, callback)
|
|
67
|
+
MyJobSystem.enqueue(callback, :on_complete, response.as_json)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def on_error(error, callback)
|
|
71
|
+
MyJobSystem.enqueue(callback, :on_error, error.as_json)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def retry
|
|
75
|
+
MyJobSystem.enqueue_job(@job_id)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Enqueue a request
|
|
80
|
+
task = PatientHttp::RequestTask.new(
|
|
81
|
+
request: PatientHttp::Request.new(:get, "https://api.example.com/data"),
|
|
82
|
+
task_handler: MyTaskHandler.new("job-123"),
|
|
83
|
+
callback: "ProcessDataCallback",
|
|
84
|
+
callback_args: {user_id: 123}
|
|
85
|
+
)
|
|
86
|
+
processor.enqueue(task)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## RequestHelper Integration Pattern
|
|
90
|
+
|
|
91
|
+
The `RequestHelper` module provides an alternative, higher-level API for making async HTTP requests. Instead of manually building `RequestTask` objects and enqueuing them to a processor, you can include the module and use convenience methods.
|
|
92
|
+
|
|
93
|
+
Key benefits:
|
|
94
|
+
- **Interface stability**: Application code making HTTP requests stays the same even when changing job queue systems
|
|
95
|
+
- **Reduced boilerplate**: No need to manually construct `Request` and `RequestTask` objects
|
|
96
|
+
- **Handler abstraction**: The registered handler encapsulates the processor/job queue integration
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```ruby
|
|
100
|
+
# Register a handler once (typically in an initializer)
|
|
101
|
+
PatientHttp.register_handler do |request:, callback:, callback_args: nil, raise_error_responses: nil|
|
|
102
|
+
task = PatientHttp::RequestTask.new(
|
|
103
|
+
request: request,
|
|
104
|
+
task_handler: MyTaskHandler.new,
|
|
105
|
+
callback: callback,
|
|
106
|
+
callback_args: callback_args,
|
|
107
|
+
raise_error_responses: raise_error_responses
|
|
108
|
+
)
|
|
109
|
+
processor.enqueue(task)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Use in your application code
|
|
113
|
+
class ApiClient
|
|
114
|
+
include PatientHttp::RequestHelper
|
|
115
|
+
|
|
116
|
+
request_template(
|
|
117
|
+
base_url: "https://api.example.com",
|
|
118
|
+
headers: {"Authorization" => "Bearer token"},
|
|
119
|
+
timeout: 60
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def fetch_user(user_id)
|
|
123
|
+
async_get(
|
|
124
|
+
"/users/#{user_id}",
|
|
125
|
+
callback: FetchUserCallback,
|
|
126
|
+
callback_args: {user_id: user_id}
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The `RequestHelper` delegates to the registered handler, passing the request details as keyword arguments. The handler translates these into whatever format your job system needs. This allows you to:
|
|
133
|
+
- Switch from Sidekiq to Solid Queue without changing `ApiClient`
|
|
134
|
+
- Use different queue systems in different environments (inline processing in tests, background jobs in production)
|
|
135
|
+
- Test request logic independently of the queue mechanism
|
|
136
|
+
|
|
137
|
+
## Request Lifecycle
|
|
138
|
+
|
|
139
|
+
```mermaid
|
|
140
|
+
sequenceDiagram
|
|
141
|
+
participant App as Application Code
|
|
142
|
+
participant Processor as Async Processor
|
|
143
|
+
participant Handler as TaskHandler
|
|
144
|
+
participant Callback as Callback Service
|
|
145
|
+
|
|
146
|
+
App->>Processor: enqueue(task)
|
|
147
|
+
activate Processor
|
|
148
|
+
Note over Processor: Task stored<br/>in queue
|
|
149
|
+
Processor-->>App: Returns immediately
|
|
150
|
+
|
|
151
|
+
Note over App: Application thread free<br/>to do other work
|
|
152
|
+
|
|
153
|
+
Processor->>Processor: Fiber reactor<br/>dequeues task
|
|
154
|
+
Processor->>Processor: Execute HTTP request<br/>(non-blocking)
|
|
155
|
+
|
|
156
|
+
alt HTTP Request Completes
|
|
157
|
+
Processor->>Handler: on_complete(response, callback)
|
|
158
|
+
Handler->>Handler: Enqueue callback job
|
|
159
|
+
Note over Callback: Job system invokes callback
|
|
160
|
+
Callback->>Callback: Process response
|
|
161
|
+
else Error Raised
|
|
162
|
+
Processor->>Handler: on_error(error, callback)
|
|
163
|
+
Handler->>Handler: Enqueue error job
|
|
164
|
+
Note over Callback: Job system invokes callback
|
|
165
|
+
Callback->>Callback: Handle error
|
|
166
|
+
end
|
|
167
|
+
deactivate Processor
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Component Relationships
|
|
171
|
+
|
|
172
|
+
```mermaid
|
|
173
|
+
erDiagram
|
|
174
|
+
PROCESSOR ||--o{ REQUEST-TASK : "manages queue of"
|
|
175
|
+
PROCESSOR ||--|| LIFECYCLE-MANAGER : "state managed by"
|
|
176
|
+
PROCESSOR ||--|| CLIENT : "uses"
|
|
177
|
+
PROCESSOR ||--|| CONFIGURATION : "configured by"
|
|
178
|
+
|
|
179
|
+
REQUEST-TASK ||--|| REQUEST : "contains"
|
|
180
|
+
REQUEST-TASK ||--|| TASK-HANDLER : "uses"
|
|
181
|
+
REQUEST-TASK ||--|| RESPONSE : "yields"
|
|
182
|
+
|
|
183
|
+
TASK-HANDLER ||--|| CALLBACK-SERVICE : "invokes"
|
|
184
|
+
|
|
185
|
+
EXTERNAL-STORAGE ||--o{ PAYLOAD-STORE : "uses"
|
|
186
|
+
|
|
187
|
+
PROCESSOR {
|
|
188
|
+
string state
|
|
189
|
+
int queue_size
|
|
190
|
+
thread reactor_thread
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
REQUEST {
|
|
194
|
+
string http_method
|
|
195
|
+
string url
|
|
196
|
+
hash headers
|
|
197
|
+
string body
|
|
198
|
+
float timeout
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
REQUEST-TASK {
|
|
202
|
+
Request request
|
|
203
|
+
TaskHandler task_handler
|
|
204
|
+
string callback
|
|
205
|
+
hash callback_args
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
RESPONSE {
|
|
209
|
+
int status
|
|
210
|
+
hash headers
|
|
211
|
+
string body
|
|
212
|
+
string http_method
|
|
213
|
+
string url
|
|
214
|
+
hash callback_args
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
TASK-HANDLER {
|
|
218
|
+
method on_complete
|
|
219
|
+
method on_error
|
|
220
|
+
method retry
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
LIFECYCLE-MANAGER {
|
|
224
|
+
string state
|
|
225
|
+
method start
|
|
226
|
+
method stop
|
|
227
|
+
method drain
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Process Model
|
|
232
|
+
|
|
233
|
+
Each application process can run:
|
|
234
|
+
- Multiple application threads
|
|
235
|
+
- **One** async HTTP processor thread
|
|
236
|
+
- **One** fiber reactor within the processor thread
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
240
|
+
│ Application Process │
|
|
241
|
+
│ │
|
|
242
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
243
|
+
│ │ Application │ │ Application │ │ Application │ │
|
|
244
|
+
│ │ Thread 1 │ │ Thread 2 │ │ Thread N │ │
|
|
245
|
+
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
|
246
|
+
│ │ │ │ │
|
|
247
|
+
│ └──────────────────┼─────────────────┘ │
|
|
248
|
+
│ │ │
|
|
249
|
+
│ ▼ │
|
|
250
|
+
│ ┌─────────────────────────┐ │
|
|
251
|
+
│ │ Async HTTP Processor │ │
|
|
252
|
+
│ │ (Dedicated Thread) │ │
|
|
253
|
+
│ │ │ │
|
|
254
|
+
│ │ ┌───────────────────┐ │ │
|
|
255
|
+
│ │ │ Fiber Reactor │ │ │
|
|
256
|
+
│ │ │ ═════════════ │ │ │
|
|
257
|
+
│ │ │ 100+ concurrent │ │ │
|
|
258
|
+
│ │ │ HTTP requests │ │ │
|
|
259
|
+
│ │ └───────────────────┘ │ │
|
|
260
|
+
│ └─────────────────────────┘ │
|
|
261
|
+
└─────────────────────────────────────────────────────────────┘
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Concurrency Model
|
|
265
|
+
|
|
266
|
+
The processor uses Ruby's Fiber scheduler (`async` gem) for non-blocking I/O:
|
|
267
|
+
|
|
268
|
+
1. **Application threads** remain free while HTTP requests execute
|
|
269
|
+
2. **Fiber reactor** multiplexes hundreds of HTTP connections
|
|
270
|
+
3. **Connection pooling** and HTTP/2 reuse connections efficiently
|
|
271
|
+
4. **TaskHandler callbacks** execute on the reactor thread and should be lightweight
|
|
272
|
+
|
|
273
|
+
## State Management
|
|
274
|
+
|
|
275
|
+
The processor maintains state through its lifecycle:
|
|
276
|
+
|
|
277
|
+
- **stopped**: Initial state, not processing requests
|
|
278
|
+
- **starting**: Processor is initializing, reactor thread launching
|
|
279
|
+
- **running**: Actively processing requests
|
|
280
|
+
- **draining**: Not accepting new requests, completing in-flight
|
|
281
|
+
- **stopping**: Shutting down, waiting for requests to finish
|
|
282
|
+
|
|
283
|
+
## Graceful Shutdown
|
|
284
|
+
|
|
285
|
+
When the processor is stopped with in-flight requests:
|
|
286
|
+
|
|
287
|
+
1. The processor stops accepting new requests (drain state)
|
|
288
|
+
2. In-flight requests are given time to complete (configurable timeout)
|
|
289
|
+
3. Any requests still pending when the timeout expires trigger `TaskHandler#retry`
|
|
290
|
+
4. The application's job system can re-enqueue these requests for later processing
|
|
291
|
+
|
|
292
|
+
## Configuration
|
|
293
|
+
|
|
294
|
+
All behavior is controlled through a central `Configuration` object:
|
|
295
|
+
|
|
296
|
+
- Maximum concurrent connections
|
|
297
|
+
- Request timeouts
|
|
298
|
+
- Connection pool settings
|
|
299
|
+
- Retry policies
|
|
300
|
+
- Proxy configuration
|
|
301
|
+
- Logging
|
|
302
|
+
- Payload stores for external storage
|
|
303
|
+
|
|
304
|
+
## External Storage
|
|
305
|
+
|
|
306
|
+
For large request/response payloads, the `ExternalStorage` class provides optional external storage:
|
|
307
|
+
|
|
308
|
+
- **PayloadStore adapters**: File, Redis, S3, ActiveRecord, or custom implementations
|
|
309
|
+
- **Automatic threshold**: Payloads exceeding a size limit are stored externally
|
|
310
|
+
- **Reference-based**: Stored payloads are replaced with lightweight references
|
|
311
|
+
- **On-demand fetch**: Original payloads are fetched when needed
|
|
312
|
+
|
|
313
|
+
## Thread Safety
|
|
314
|
+
|
|
315
|
+
- **Thread-safe queues**: `Thread::Queue` for request enqueueing
|
|
316
|
+
- **Atomic operations**: `Concurrent::AtomicReference` for state
|
|
317
|
+
- **Synchronized access**: Mutexes protect shared data structures
|
|
318
|
+
- **Immutable values**: Request/Response are immutable once created
|
|
319
|
+
|
|
320
|
+
## Further Reading
|
|
321
|
+
|
|
322
|
+
- [README](README.md)
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## 1.0.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Async HTTP processor that runs in a dedicated thread with a Fiber-based reactor, allowing hundreds of concurrent HTTP requests without blocking application threads.
|
|
12
|
+
- Pluggable `TaskHandler` interface for integrating with any job system or application framework.
|
|
13
|
+
- Callback system with `on_complete` and `on_error` handlers for processing HTTP responses and errors.
|
|
14
|
+
- `Request` and `RequestTemplate` classes for building HTTP requests with support for all HTTP methods (GET, POST, PUT, PATCH, DELETE).
|
|
15
|
+
- `RequestTemplate` for repeated requests to the same API with shared configuration (base URL, headers, timeouts).
|
|
16
|
+
- JSON-serializable `Response` and error objects for safe passing through job queues and across process boundaries.
|
|
17
|
+
- Automatic redirect following with configurable maximum redirects.
|
|
18
|
+
- HTTP/2 support via the async-http gem.
|
|
19
|
+
- Connection pooling with configurable pool size for efficient reuse of connections across hosts.
|
|
20
|
+
- External payload storage system with adapters for File, Redis, and S3 to handle large request/response payloads.
|
|
21
|
+
- Configurable response size limits to bound memory usage.
|
|
22
|
+
- Proxy support for HTTP/HTTPS proxies with authentication.
|
|
23
|
+
- Automatic retry support for failed requests.
|
|
24
|
+
- Graceful shutdown with configurable timeout and automatic retry of incomplete requests via `TaskHandler#retry`.
|
|
25
|
+
- `ProcessorObserver` interface for monitoring processor events (request start/end, errors, capacity exceeded).
|
|
26
|
+
- `SynchronousExecutor` for testing without starting the async processor.
|
|
27
|
+
- Configurable connection limits, timeouts, response size limits, and User-Agent headers.
|
|
28
|
+
- Typed error classes (`HttpError`, `ClientError`, `ServerError`, `RedirectError`, `RequestError`) for precise error handling.
|
|
29
|
+
- Optional treatment of non-2xx HTTP responses as errors via `raise_error_responses` configuration.
|
|
30
|
+
- `CallbackArgs` for passing custom data through the request/response cycle.
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2026 Brian Durand
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|