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/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, 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

Class Method Details

.delete(uri, callback:, **kwargs) ⇒ Object

Enqueues an HTTP DELETE request.

Parameters:

  • uri (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • kwargs (Hash)

    forwarded to request

Returns:

  • (Object)

    return value from the registered request handler



226
227
228
# File 'lib/patient_http.rb', line 226

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.

Parameters:

  • request (Request)

    the HTTP request to handle

  • callback (Class, String)

    the callback class or name

  • callback_args (Hash, nil) (defaults to: nil)

    JSON-compatible callback arguments

  • raise_error_responses (Boolean, nil) (defaults to: nil)

    when true, non-success responses are reported as errors

Returns:

  • (Object)

    return value from the registered request handler

Raises:

  • (RuntimeError)

    if no handler is registered



165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/patient_http.rb', line 165

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

.get(uri, callback:, **kwargs) ⇒ Object

Enqueues an HTTP GET request.

Parameters:

  • uri (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • kwargs (Hash)

    forwarded to request

Returns:

  • (Object)

    return value from the registered request handler



186
187
188
# File 'lib/patient_http.rb', line 186

def get(uri, callback:, **kwargs)
  request(:get, uri, callback: callback, **kwargs)
end

.patch(uri, callback:, **kwargs) ⇒ Object

Enqueues an HTTP PATCH request.

Parameters:

  • uri (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • kwargs (Hash)

    forwarded to request

Returns:

  • (Object)

    return value from the registered request handler



216
217
218
# File 'lib/patient_http.rb', line 216

def patch(uri, callback:, **kwargs)
  request(:patch, uri, callback: callback, **kwargs)
end

.post(uri, callback:, **kwargs) ⇒ Object

Enqueues an HTTP POST request.

Parameters:

  • uri (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • kwargs (Hash)

    forwarded to request

Returns:

  • (Object)

    return value from the registered request handler



196
197
198
# File 'lib/patient_http.rb', line 196

def post(uri, callback:, **kwargs)
  request(:post, uri, callback: callback, **kwargs)
end

.put(uri, callback:, **kwargs) ⇒ Object

Enqueues an HTTP PUT request.

Parameters:

  • uri (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • kwargs (Hash)

    forwarded to request

Returns:

  • (Object)

    return value from the registered request handler



206
207
208
# File 'lib/patient_http.rb', line 206

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.

Parameters:

  • callable (#call, nil) (defaults to: nil)

    A callable object that will handle requests.

Yields:

  • (request, callback, callback_args, raise_error_responses)

    If a block is given, it will be used as the request handler

Returns:

  • (#call)

    the registered handler

Raises:

  • (ArgumentError)

    if neither a callable nor a block is provided, or if both are provided

  • (ArgumentError)

    if the provided callable does not respond to call

  • (ArgumentError)

    if the handler does not support the required keyword arguments



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/patient_http.rb', line 110

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.

Parameters:

  • callable (#call, nil) (defaults to: nil)

    A callable object that will handle requests.

Yields:

  • (request, callback, callback_args, raise_error_responses)

    If a block is given, it will be used as the request handler

Returns:

  • (#call)

    the registered handler

Raises:

  • (RuntimeError)

    if a handler is already registered

  • (ArgumentError)

    if neither a callable nor a block is provided, or if both are provided

  • (ArgumentError)

    if the provided callable does not respond to call

  • (ArgumentError)

    if the handler does not support the required keyword arguments



135
136
137
138
139
140
141
142
143
# File 'lib/patient_http.rb', line 135

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

.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.

Parameters:

  • method (Symbol)

    HTTP method (:get, :post, :put, :patch, :delete)

  • url (String)

    absolute URL

  • callback (Class, String)

    callback class to handle the response

  • headers (Hash, nil) (defaults to: nil)

    request headers

  • body (String, nil) (defaults to: nil)

    raw request body

  • json (Hash, Array, nil) (defaults to: nil)

    JSON payload encoded by the request layer

  • params (Hash, nil) (defaults to: nil)

    query parameters

  • timeout (Numeric, nil) (defaults to: nil)

    timeout in seconds for this request

  • raise_error_responses (Boolean, nil) (defaults to: nil)

    when true, non-success responses are reported as errors

  • callback_args (Hash, nil) (defaults to: nil)

    JSON-compatible callback arguments

  • preprocessors (String, Symbol, Array<String, Symbol>, nil) (defaults to: nil)

    names of preprocessors registered on the configuration to apply to the request when it is sent

Returns:

  • (Object)

    return value from the registered request handler



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/patient_http.rb', line 246

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.

Parameters:

  • name (String, Symbol)

    the name of the secret to reference

Returns:

See Also:



286
287
288
# File 'lib/patient_http.rb', line 286

def secret(name)
  SecretReference.new(name)
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.



93
94
95
# File 'lib/patient_http.rb', line 93

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.

Returns:

  • (Boolean)


86
87
88
# File 'lib/patient_http.rb', line 86

def testing?
  @testing
end

.unregister_handler(handler = nil) ⇒ void

This method returns an undefined value.

Unregisters the current request handler.

Parameters:

  • handler (#call, nil) (defaults to: nil)

    If provided, only unregisters if the given handler matches the current handler



150
151
152
153
154
# File 'lib/patient_http.rb', line 150

def unregister_handler(handler = nil)
  @handler_mutex.synchronize do
    @handler = nil if @handler == handler || handler.nil?
  end
end