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/completion_executor.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, CompletionExecutor, 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, UnknownProcessorError
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, processor: 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.
432 433 434 |
# File 'lib/patient_http.rb', line 432 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.
442 443 444 445 446 447 |
# File 'lib/patient_http.rb', line 442 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.
314 315 316 |
# File 'lib/patient_http.rb', line 314 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.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/patient_http.rb', line 253 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.
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/patient_http.rb', line 226 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.
274 275 276 |
# File 'lib/patient_http.rb', line 274 def get(uri, callback:, **kwargs) request(:get, uri, callback: callback, **kwargs) end |
.handler_registered? ⇒ Boolean
Check if a request handler is registered.
209 210 211 |
# File 'lib/patient_http.rb', line 209 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.
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/patient_http.rb', line 181 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!.
202 203 204 |
# File 'lib/patient_http.rb', line 202 def inline? @handler_mutex.synchronize { !@handler.nil? && @handler.equal?(@inline_handler) } end |
.patch(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PATCH request.
304 305 306 |
# File 'lib/patient_http.rb', line 304 def patch(uri, callback:, **kwargs) request(:patch, uri, callback: callback, **kwargs) end |
.post(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP POST request.
284 285 286 |
# File 'lib/patient_http.rb', line 284 def post(uri, callback:, **kwargs) request(:post, uri, callback: callback, **kwargs) end |
.put(uri, callback:, **kwargs) ⇒ Object
Enqueues an HTTP PUT request.
294 295 296 |
# File 'lib/patient_http.rb', line 294 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.
123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/patient_http.rb', line 123 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.
148 149 150 151 152 153 154 155 156 |
# File 'lib/patient_http.rb', line 148 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.
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/patient_http.rb', line 396 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, processor: nil) ⇒ Object
Builds and dispatches an HTTP request.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/patient_http.rb', line 336 def request( method, url, callback:, headers: nil, body: nil, json: nil, params: nil, timeout: nil, raise_error_responses: nil, callback_args: nil, preprocessors: nil, processor: nil ) request = Request.new( method, url, body: body, json: json, headers: headers, params: params, timeout: timeout, preprocessors: preprocessors, processor: processor ) 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.
378 379 380 |
# File 'lib/patient_http.rb', line 378 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.
418 419 420 421 422 423 424 |
# File 'lib/patient_http.rb', line 418 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.
106 107 108 |
# File 'lib/patient_http.rb', line 106 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.
99 100 101 |
# File 'lib/patient_http.rb', line 99 def testing? @testing end |
.unregister_handler(handler = nil) ⇒ void
This method returns an undefined value.
Unregisters the current request handler.
163 164 165 166 167 |
# File 'lib/patient_http.rb', line 163 def unregister_handler(handler = nil) @handler_mutex.synchronize do @handler = nil if @handler == handler || handler.nil? end end |