Module: PatientHttp
- Defined in:
- lib/patient_http.rb,
lib/patient_http/error.rb,
lib/patient_http/client.rb,
lib/patient_http/payload.rb,
lib/patient_http/request.rb,
lib/patient_http/response.rb,
lib/patient_http/encryptor.rb,
lib/patient_http/processor.rb,
lib/patient_http/http_error.rb,
lib/patient_http/client_pool.rb,
lib/patient_http/time_helper.rb,
lib/patient_http/class_helper.rb,
lib/patient_http/http_headers.rb,
lib/patient_http/rails/engine.rb,
lib/patient_http/request_task.rb,
lib/patient_http/task_handler.rb,
lib/patient_http/callback_args.rb,
lib/patient_http/configuration.rb,
lib/patient_http/payload_store.rb,
lib/patient_http/request_error.rb,
lib/patient_http/redirect_error.rb,
lib/patient_http/request_helper.rb,
lib/patient_http/secret_manager.rb,
lib/patient_http/redirect_helper.rb,
lib/patient_http/response_reader.rb,
lib/patient_http/external_storage.rb,
lib/patient_http/outgoing_request.rb,
lib/patient_http/request_preparer.rb,
lib/patient_http/request_template.rb,
lib/patient_http/secret_reference.rb,
lib/patient_http/lifecycle_manager.rb,
lib/patient_http/callback_validator.rb,
lib/patient_http/payload_store/base.rb,
lib/patient_http/processor_observer.rb,
lib/patient_http/inline_task_handler.rb,
lib/patient_http/synchronous_executor.rb,
lib/patient_http/payload_store/s3_store.rb,
lib/patient_http/payload_store/file_store.rb,
lib/patient_http/payload_store/redis_store.rb,
lib/patient_http/payload_store/active_record_store.rb
Overview
Generic async HTTP connection pool for Ruby applications.
This module provides:
- Async HTTP request processing using Ruby's Fiber scheduler
- Connection pooling with HTTP/2 support
- Configurable timeouts, retries, and proxy support
- Error handling with typed errors
This module can be used standalone or integrated with job systems like Sidekiq via adapters.
Defined Under Namespace
Modules: CallbackValidator, ClassHelper, PayloadStore, Rails, RedirectHelper, RequestHelper, TimeHelper Classes: CallbackArgs, Client, ClientError, ClientPool, Configuration, Encryptor, Error, ExternalStorage, HttpError, HttpHeaders, InlineTaskHandler, LifecycleManager, MaxCapacityError, NotRunningError, OutgoingRequest, Payload, Processor, ProcessorObserver, RecursiveRedirectError, RedirectError, Request, RequestError, RequestPreparer, RequestTask, RequestTemplate, Response, ResponseReader, ResponseTooLargeError, SecretManager, SecretReference, ServerError, SynchronousExecutor, TaskHandler, TooManyRedirectsError
Constant Summary collapse
- FOLLOWABLE_REDIRECT_STATUSES =
HTTP redirect status codes that should be followed
[301, 302, 303, 307, 308].freeze
- VERSION =
File.read(File.join(__dir__, "../VERSION")).strip
Class Method Summary collapse
-
.default_configuration ⇒ Configuration?
The default configuration used for inline execution when none is provided.
-
.default_configuration=(config) ⇒ void
Set the default configuration.
-
.delete(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP DELETE request.
-
.execute(request:, callback:, callback_args: nil, raise_error_responses: nil) ⇒ Object
Executes the registered request handler with the given request parameters.
-
.execute_inline(request:, callback:, callback_args: nil, raise_error_responses: nil, config: nil) ⇒ String
Executes a request inline (synchronously, in-process) through SynchronousExecutor, invoking the callback with the response or error before returning.
-
.get(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP GET request.
-
.handler_registered? ⇒ Boolean
Check if a request handler is registered.
-
.inline!(config: nil) ⇒ void
Registers a request handler that executes requests inline (synchronously, in-process) instead of dispatching them to a job system.
-
.inline? ⇒ Boolean
Check if the currently registered handler is the inline handler registered by PatientHttp.inline!.
-
.patch(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PATCH request.
-
.post(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP POST request.
-
.put(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PUT request.
-
.register_handler(callable = nil) {|request, callback, callback_args, raise_error_responses| ... } ⇒ #call
Registers a request handler that will be called to process each request.
-
.register_handler!(callable = nil) {|request, callback, callback_args, raise_error_responses| ... } ⇒ #call
Registers a request handler, raising an error if one is already registered.
-
.register_secret(name, value = nil) {|name| ... } ⇒ void
Register a named secret at the module level, independent of any configuration.
-
.request(method, url, callback:, headers: nil, body: nil, json: nil, params: nil, timeout: nil, raise_error_responses: nil, callback_args: nil, preprocessors: nil) ⇒ Object
Builds and dispatches an HTTP request.
-
.secret(name) ⇒ SecretReference
Build a reference to a named secret for use as a sensitive header or query parameter value when building a request.
-
.secret_registered?(name) ⇒ Boolean
Check if a secret name is registered, either at the module level via PatientHttp.register_secret or on the PatientHttp.default_configuration.
-
.testing=(value) ⇒ Object
private
Set testing mode.
-
.testing? ⇒ Boolean
private
Check if running in testing mode.
-
.unregister_handler(handler = nil) ⇒ void
Unregisters the current request handler.
Class Method Details
.default_configuration ⇒ Configuration?
The default configuration used for inline execution when none is provided. Job-system integration gems should set this at the end of their configure step so that module-level secrets registered with register_secret are applied to the configuration the processor runs with.
421 422 423 |
# File 'lib/patient_http.rb', line 421 def default_configuration @config_mutex.synchronize { @default_configuration } end |
.default_configuration=(config) ⇒ void
This method returns an undefined value.
Set the default configuration. Any secrets registered with register_secret are applied to it; the module-level registry is retained, so re-assigning a new configuration re-applies the same secrets.
431 432 433 434 435 436 |
# File 'lib/patient_http.rb', line 431 def default_configuration=(config) @config_mutex.synchronize do @default_configuration = config apply_module_secrets(config) if config end end |
.delete(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP DELETE request.
307 308 309 |
# File 'lib/patient_http.rb', line 307 def delete(uri, callback:, **kwargs) request(:delete, uri, callback: callback, **kwargs) end |
.execute(request:, callback:, callback_args: nil, raise_error_responses: nil) ⇒ Object
Executes the registered request handler with the given request parameters.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/patient_http.rb', line 246 def execute(request:, callback:, callback_args: nil, raise_error_responses: nil) handler = @handler_mutex.synchronize { @handler } unless handler raise "No request handler registered; you must register a PatientHttp handler before executing requests" end handler.call( request: request, callback: callback, callback_args: callback_args, raise_error_responses: raise_error_responses ) end |
.execute_inline(request:, callback:, callback_args: nil, raise_error_responses: nil, config: nil) ⇒ String
Executes a request inline (synchronously, in-process) through SynchronousExecutor, invoking the callback with the response or error before returning.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/patient_http.rb', line 219 def execute_inline(request:, callback:, callback_args: nil, raise_error_responses: nil, config: nil) config ||= default_configuration || inline_configuration raise_error_responses = config.raise_error_responses if raise_error_responses.nil? task = RequestTask.new( request: request, task_handler: InlineTaskHandler.new, callback: callback, callback_args: callback_args, raise_error_responses: raise_error_responses, default_max_redirects: config.max_redirects ) SynchronousExecutor.new(task, config: config).call task.id end |
.get(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP GET request.
267 268 269 |
# File 'lib/patient_http.rb', line 267 def get(uri, callback:, **kwargs) request(:get, uri, callback: callback, **kwargs) end |
.handler_registered? ⇒ Boolean
Check if a request handler is registered.
202 203 204 |
# File 'lib/patient_http.rb', line 202 def handler_registered? @handler_mutex.synchronize { !@handler.nil? } end |
.inline!(config: nil) ⇒ void
This method returns an undefined value.
Registers a request handler that executes requests inline (synchronously, in-process) instead of dispatching them to a job system.
This is intended for consoles, tests, and development environments where no job-system integration gem is configured. Each request runs through SynchronousExecutor and the callback is invoked on the calling thread before the handler returns.
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/patient_http.rb', line 174 def inline!(config: nil) handler = lambda do |request:, callback:, callback_args: nil, raise_error_responses: nil| execute_inline( request: request, callback: callback, callback_args: callback_args, raise_error_responses: raise_error_responses, config: config ) end @handler_mutex.synchronize do register_handler(handler) @inline_handler = handler end end |
.inline? ⇒ Boolean
Check if the currently registered handler is the inline handler registered by inline!.
195 196 197 |
# File 'lib/patient_http.rb', line 195 def inline? @handler_mutex.synchronize { !@handler.nil? && @handler.equal?(@inline_handler) } end |
.patch(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PATCH request.
297 298 299 |
# File 'lib/patient_http.rb', line 297 def patch(uri, callback:, **kwargs) request(:patch, uri, callback: callback, **kwargs) end |
.post(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP POST request.
277 278 279 |
# File 'lib/patient_http.rb', line 277 def post(uri, callback:, **kwargs) request(:post, uri, callback: callback, **kwargs) end |
.put(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PUT request.
287 288 289 |
# File 'lib/patient_http.rb', line 287 def put(uri, callback:, **kwargs) request(:put, uri, callback: callback, **kwargs) end |
.register_handler(callable = nil) {|request, callback, callback_args, raise_error_responses| ... } ⇒ #call
Registers a request handler that will be called to process each request.
The handler must be a callable object (responds to call) or a block.
The handler will receive keyword arguments: request, callback, callback_args, and raise_error_responses. It should return the request id for the enqueued request.
116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/patient_http.rb', line 116 def register_handler(callable = nil, &block) raise ArgumentError.new("Must provide a callable object or a block") unless callable || block_given? raise ArgumentError.new("Cannot provide both a callable object and a block") if callable && block_given? handler = callable || block raise ArgumentError.new("Handler must be a callable object or a block") unless handler.respond_to?(:call) validate_handler_parameters!(handler) @handler_mutex.synchronize { @handler = handler } end |
.register_handler!(callable = nil) {|request, callback, callback_args, raise_error_responses| ... } ⇒ #call
Registers a request handler, raising an error if one is already registered.
This is a safer alternative to register_handler that prevents accidental double-registration.
141 142 143 144 145 146 147 148 149 |
# File 'lib/patient_http.rb', line 141 def register_handler!(callable = nil, &block) @handler_mutex.synchronize do if @handler raise "A PatientHttp handler is already registered. Unregister the existing handler before registering a new one." end register_handler(callable, &block) end end |
.register_secret(name, value = nil) {|name| ... } ⇒ void
This method returns an undefined value.
Register a named secret at the module level, independent of any configuration.
Secrets registered here are applied to the default_configuration (immediately if one is already set, or when one is set later) and to the configuration used for inline execution. This makes boot order irrelevant: application code can register secrets before or after the job-system integration gem configures the processor.
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/patient_http.rb', line 385 def register_secret(name, value = nil, &block) if value.nil? && block.nil? raise ArgumentError.new("register_secret requires a value or a block") end if !value.nil? && block raise ArgumentError.new("register_secret accepts either a value or a block, not both") end @config_mutex.synchronize do secret_value = block || value @module_secrets[name.to_s] = secret_value @default_configuration&.register_secret(name, secret_value) @inline_configuration&.register_secret(name, secret_value) end end |
.request(method, url, callback:, headers: nil, body: nil, json: nil, params: nil, timeout: nil, raise_error_responses: nil, callback_args: nil, preprocessors: nil) ⇒ Object
Builds and dispatches an HTTP request.
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/patient_http.rb', line 327 def request( method, url, callback:, headers: nil, body: nil, json: nil, params: nil, timeout: nil, raise_error_responses: nil, callback_args: nil, preprocessors: nil ) request = Request.new( method, url, body: body, json: json, headers: headers, params: params, timeout: timeout, preprocessors: preprocessors ) execute( request: request, callback: callback, callback_args: callback_args, raise_error_responses: raise_error_responses ) end |
.secret(name) ⇒ SecretReference
Build a reference to a named secret for use as a sensitive header or query parameter value when building a request.
The reference holds only the secret's name; the value is resolved on the processor side at send time using the secrets registered on the configuration.
367 368 369 |
# File 'lib/patient_http.rb', line 367 def secret(name) SecretReference.new(name) end |
.secret_registered?(name) ⇒ Boolean
Check if a secret name is registered, either at the module level via register_secret or on the default_configuration.
407 408 409 410 411 412 413 |
# File 'lib/patient_http.rb', line 407 def secret_registered?(name) @config_mutex.synchronize do return true if @module_secrets.include?(name.to_s) !@default_configuration.nil? && @default_configuration.secret_manager.include?(name) end end |
.testing=(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Set testing mode.
99 100 101 |
# File 'lib/patient_http.rb', line 99 def testing=(value) @testing = !!value end |
.testing? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Check if running in testing mode.
92 93 94 |
# File 'lib/patient_http.rb', line 92 def testing? @testing end |
.unregister_handler(handler = nil) ⇒ void
This method returns an undefined value.
Unregisters the current request handler.
156 157 158 159 160 |
# File 'lib/patient_http.rb', line 156 def unregister_handler(handler = nil) @handler_mutex.synchronize do @handler = nil if @handler == handler || handler.nil? end end |