Class: PatientHttp::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/patient_http/configuration.rb

Overview

Configuration for the PatientHttp processor.

This class holds all configuration options for the HTTP connection pool, including connection limits, timeouts, and other HTTP client settings. It has no dependencies on any job system.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_connections: 256, request_timeout: 60, shutdown_timeout: 30, logger: nil, max_response_size: 1024 * 1024, user_agent: "PatientHttp", raise_error_responses: false, max_redirects: 5, connection_pool_size: 100, connection_timeout: nil, proxy_url: nil, retries: 3, protocol: nil, encryption_key: nil, max_connections_per_host: nil, completion_threads: 2, completion_retries: 2) ⇒ Configuration

Initializes a new Configuration with the specified options.

Parameters:

  • max_connections (Integer) (defaults to: 256)

    Maximum number of concurrent connections

  • request_timeout (Numeric) (defaults to: 60)

    Default request timeout in seconds

  • shutdown_timeout (Numeric) (defaults to: 30)

    Graceful shutdown timeout in seconds

  • logger (Logger, nil) (defaults to: nil)

    Logger instance to use (defaults to stdout)

  • max_response_size (Integer) (defaults to: 1024 * 1024)

    Maximum response size in bytes

  • user_agent (String, nil) (defaults to: "PatientHttp")

    Default User-Agent header value

  • raise_error_responses (Boolean) (defaults to: false)

    Whether to raise HttpError for non-2xx responses by default

  • max_redirects (Integer) (defaults to: 5)

    Maximum number of redirects to follow (0 disables redirects)

  • connection_pool_size (Integer) (defaults to: 100)

    Maximum number of host clients to pool

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

    Connection timeout in seconds

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

    HTTP/HTTPS proxy URL (supports authentication)

  • retries (Integer) (defaults to: 3)

    Number of retries for failed requests

  • protocol (Symbol, nil) (defaults to: nil)

    HTTP protocol to use (:http1 or :http2); nil to negotiate

  • max_connections_per_host (Integer, nil) (defaults to: nil)

    Maximum number of connections per host (nil for unlimited)

  • completion_threads (Integer) (defaults to: 2)

    Number of threads that deliver completed results

  • completion_retries (Integer) (defaults to: 2)

    Number of retries when delivering a completed result fails. A retry calls TaskHandler#on_complete or #on_error again, so a handler that raises after its side effect delivers the callback more than once unless it is idempotent. Set to 0 to report the first failure without retrying.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/patient_http/configuration.rb', line 87

def initialize(
  max_connections: 256,
  request_timeout: 60,
  shutdown_timeout: 30,
  logger: nil,
  max_response_size: 1024 * 1024,
  user_agent: "PatientHttp",
  raise_error_responses: false,
  max_redirects: 5,
  connection_pool_size: 100,
  connection_timeout: nil,
  proxy_url: nil,
  retries: 3,
  protocol: nil,
  encryption_key: nil,
  max_connections_per_host: nil,
  completion_threads: 2,
  completion_retries: 2
)
  @mutex = Mutex.new

  # Initialize payload store configuration
  @payload_stores = {}
  @default_payload_store_name = nil

  # Initialize secret configuration
  @secrets = {}
  @secret_manager = SecretManager.new

  # Initialize preprocessor registry
  @preprocessors = {}

  @encryptor = nil

  self.max_connections = max_connections
  self.request_timeout = request_timeout
  self.shutdown_timeout = shutdown_timeout
  self.logger = logger || Logger.new($stderr, level: Logger::ERROR)
  self.max_response_size = max_response_size
  self.user_agent = user_agent
  self.raise_error_responses = raise_error_responses
  self.max_redirects = max_redirects
  self.connection_pool_size = connection_pool_size
  self.connection_timeout = connection_timeout
  self.proxy_url = proxy_url
  self.retries = retries
  self.protocol = protocol
  self.encryption_key = encryption_key
  self.max_connections_per_host = max_connections_per_host
  self.completion_threads = completion_threads
  self.completion_retries = completion_retries
end

Instance Attribute Details

#completion_retriesInteger

Returns Number of retries when delivering a completed result fails. A retry calls the task handler again, so handlers must be idempotent.

Returns:

  • (Integer)

    Number of retries when delivering a completed result fails. A retry calls the task handler again, so handlers must be idempotent.



26
27
28
# File 'lib/patient_http/configuration.rb', line 26

def completion_retries
  @completion_retries
end

#completion_threadsInteger

Returns Number of threads that deliver completed results.

Returns:

  • (Integer)

    Number of threads that deliver completed results



22
23
24
# File 'lib/patient_http/configuration.rb', line 22

def completion_threads
  @completion_threads
end

#connection_pool_sizeInteger

Returns This is the maximum number of hosts for which connections will be kept alive for at one time.

Returns:

  • (Integer)

    This is the maximum number of hosts for which connections will be kept alive for at one time.



48
49
50
# File 'lib/patient_http/configuration.rb', line 48

def connection_pool_size
  @connection_pool_size
end

#connection_timeoutNumeric?

Returns Connection timeout in seconds.

Returns:

  • (Numeric, nil)

    Connection timeout in seconds



51
52
53
# File 'lib/patient_http/configuration.rb', line 51

def connection_timeout
  @connection_timeout
end

#default_payload_store_nameSymbol? (readonly)

Get the name of the default payload store.

Returns:

  • (Symbol, nil)

    The default store name or nil if none registered



407
408
409
# File 'lib/patient_http/configuration.rb', line 407

def default_payload_store_name
  @default_payload_store_name
end

#loggerLogger

Get the logger to use to report pool events. Default is to log errors to STDERR.

Returns:

  • (Logger)

    the logger instance



142
143
144
# File 'lib/patient_http/configuration.rb', line 142

def logger
  @logger
end

#max_connectionsInteger

Returns Maximum number of concurrent connections.

Returns:

  • (Integer)

    Maximum number of concurrent connections



16
17
18
# File 'lib/patient_http/configuration.rb', line 16

def max_connections
  @max_connections
end

#max_connections_per_hostInteger?

Returns Maximum number of connections per host (nil for unlimited).

Returns:

  • (Integer, nil)

    Maximum number of connections per host (nil for unlimited)



19
20
21
# File 'lib/patient_http/configuration.rb', line 19

def max_connections_per_host
  @max_connections_per_host
end

#max_redirectsInteger

Returns Maximum number of redirects to follow (0 disables redirects).

Returns:

  • (Integer)

    Maximum number of redirects to follow (0 disables redirects)



44
45
46
# File 'lib/patient_http/configuration.rb', line 44

def max_redirects
  @max_redirects
end

#max_response_sizeInteger

Returns Maximum response size in bytes.

Returns:

  • (Integer)

    Maximum response size in bytes



35
36
37
# File 'lib/patient_http/configuration.rb', line 35

def max_response_size
  @max_response_size
end

#protocolSymbol?

Returns HTTP protocol to use (:http1 or :http2). When nil, the protocol is negotiated with the server (HTTP/2 preferred for HTTPS).

Returns:

  • (Symbol, nil)

    HTTP protocol to use (:http1 or :http2). When nil, the protocol is negotiated with the server (HTTP/2 preferred for HTTPS).



61
62
63
# File 'lib/patient_http/configuration.rb', line 61

def protocol
  @protocol
end

#proxy_urlString?

Returns HTTP/HTTPS proxy URL (supports authentication).

Returns:

  • (String, nil)

    HTTP/HTTPS proxy URL (supports authentication)



54
55
56
# File 'lib/patient_http/configuration.rb', line 54

def proxy_url
  @proxy_url
end

#raise_error_responsesBoolean

Returns Whether to raise HttpError for non-2xx responses by default.

Returns:

  • (Boolean)

    Whether to raise HttpError for non-2xx responses by default



41
42
43
# File 'lib/patient_http/configuration.rb', line 41

def raise_error_responses
  @raise_error_responses
end

#request_timeoutNumeric

Returns Default request timeout in seconds.

Returns:

  • (Numeric)

    Default request timeout in seconds



29
30
31
# File 'lib/patient_http/configuration.rb', line 29

def request_timeout
  @request_timeout
end

#retriesInteger

Returns Number of retries for failed requests.

Returns:

  • (Integer)

    Number of retries for failed requests



57
58
59
# File 'lib/patient_http/configuration.rb', line 57

def retries
  @retries
end

#secret_managerSecretManager (readonly)

Returns the secret manager instance.

Returns:



64
65
66
# File 'lib/patient_http/configuration.rb', line 64

def secret_manager
  @secret_manager
end

#shutdown_timeoutNumeric

Returns Graceful shutdown timeout in seconds.

Returns:

  • (Numeric)

    Graceful shutdown timeout in seconds



32
33
34
# File 'lib/patient_http/configuration.rb', line 32

def shutdown_timeout
  @shutdown_timeout
end

#user_agentString?

Returns Default User-Agent header value.

Returns:

  • (String, nil)

    Default User-Agent header value



38
39
40
# File 'lib/patient_http/configuration.rb', line 38

def user_agent
  @user_agent
end

Instance Method Details

#decryption(callable = nil) {|data| ... } ⇒ Object

Set the decryption callable for decrypting payloads after deserialization.

Parameters:

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

    An object that responds to #call, taking data and returning decrypted data

Yields:

  • (data)

    A block that takes data and returns decrypted data

Raises:

  • (ArgumentError)

    If both callable and block are provided, or if callable doesn't respond to #call



248
249
250
251
# File 'lib/patient_http/configuration.rb', line 248

def decryption(callable = nil, &block)
  @decryption = resolve_callable(:decryption, callable, &block)
  @encryptor = nil
end

#encryption(callable = nil) {|data| ... } ⇒ Object

Set the encryption callable for encrypting payloads before serialization.

Parameters:

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

    An object that responds to #call, taking data and returning encrypted data

Yields:

  • (data)

    A block that takes data and returns encrypted data

Raises:

  • (ArgumentError)

    If both callable and block are provided, or if callable doesn't respond to #call



238
239
240
241
# File 'lib/patient_http/configuration.rb', line 238

def encryption(callable = nil, &block)
  @encryption = resolve_callable(:encryption, callable, &block)
  @encryptor = nil
end

#encryption_key=(keys) ⇒ Object



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/patient_http/configuration.rb', line 253

def encryption_key=(keys)
  keys = Array(keys).map(&:to_s).reject(&:empty?)
  if keys.empty?
    @encryption = nil
    @decryption = nil
    @encryptor = nil
    return
  end

  unless defined?(ActiveSupport::MessageEncryptor)
    begin
      require "active_support/key_generator"
      require "active_support/message_encryptor"
    rescue LoadError
      raise ArgumentError.new("ActiveSupport::MessageEncryptor is required for encryption_key")
    end
  end

  key_length = ActiveSupport::MessageEncryptor.key_len
  key_generator = lambda do |key|
    ActiveSupport::KeyGenerator.new(key).generate_key(SALT, key_length)
  end

  encryptor = ActiveSupport::MessageEncryptor.new(key_generator.call(keys.first), cipher: "aes-256-gcm")
  keys[1..].each { |key| encryptor.rotate(key_generator.call(key)) }

  encryption { |data| encryptor.encrypt_and_sign(data) }
  decryption { |data| encryptor.decrypt_and_verify(data) }
  @encryptor = nil
end

#encryptorEncryptor

Return an Encryptor instance. If encryption and decryption are not set, then this will be an empty Encryptor that returns data unchanged.

Returns:



288
289
290
# File 'lib/patient_http/configuration.rb', line 288

def encryptor
  @encryptor ||= Encryptor.new(encryption: @encryption, decryption: @decryption)
end

#payload_store(name = nil) ⇒ PayloadStore::Base?

Get a registered payload store by name.

Parameters:

  • name (Symbol, String, nil) (defaults to: nil)

    Store name. If nil, returns the default store.

Returns:



394
395
396
397
398
399
400
401
402
# File 'lib/patient_http/configuration.rb', line 394

def payload_store(name = nil)
  if name.nil?
    return nil unless @default_payload_store_name

    @payload_stores[@default_payload_store_name]
  else
    @payload_stores[name.to_sym]
  end
end

#payload_storesHash{Symbol => PayloadStore::Base}

Get all registered payload stores.

Returns:



412
413
414
# File 'lib/patient_http/configuration.rb', line 412

def payload_stores
  @payload_stores.dup
end

#preprocessor(name) ⇒ #call?

Get a registered preprocessor by name.

Parameters:

  • name (String, Symbol)

    the preprocessor name

Returns:

  • (#call, nil)

    the preprocessor or nil if not registered



352
353
354
# File 'lib/patient_http/configuration.rb', line 352

def preprocessor(name)
  @preprocessors[name.to_s]
end

#register_payload_store(name, adapter:, **options) ⇒ void

This method returns an undefined value.

Register a payload store for external storage of large payloads.

The name is included in the serialized references to the stored data. Changing it will cause any existing reference to become invalid.

Multiple stores can be registered for migration purposes. The last store registered becomes the default used for new writes. References to other registered stores remain valid for reading.

Parameters:

  • name (Symbol, String)

    Unique name for this store registration

  • adapter (Symbol, String)

    The adapter type (:file, :redis, :s3, etc.)

  • options (Hash)

    Options passed to the adapter constructor

Raises:

  • (ArgumentError)

    If the adapter is not registered



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/patient_http/configuration.rb', line 370

def register_payload_store(name, adapter:, **options)
  name = name.to_sym
  adapter = adapter.to_sym

  # Trigger autoload for common adapters
  ensure_adapter_loaded(adapter)

  unless PayloadStore::Base.lookup(adapter)
    raise ArgumentError, "Unknown payload store adapter: #{adapter.inspect}. " \
      "Available adapters: #{PayloadStore::Base.registered_adapters.inspect}"
  end

  store = PayloadStore::Base.create(adapter, **options)

  @mutex.synchronize do
    @payload_stores = @payload_stores.merge(name => store)
    @default_payload_store_name = name
  end
end

#register_preprocessor(name, callable = nil) {|outgoing_request| ... } ⇒ void

This method returns an undefined value.

Register a named preprocessor that can be attached to requests to modify them just before they are sent -- for example, to sign requests.

The preprocessor can be provided as a callable or a block taking a single argument. When a request that references the preprocessor is sent, it is invoked with an OutgoingRequest after secret references have been resolved and the x-request-id and default user-agent headers have been set. It can change the request headers and append query parameters.

Requests reference preprocessors by name only, so the callable (and any credentials it uses) stays on the processor side and is never serialized.

Parameters:

  • name (String, Symbol)

    the preprocessor name

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

    object invoked with the outgoing request (omit when providing a block)

Yields:

  • (outgoing_request)

    a block invoked with the outgoing request (omit when providing a callable)

Raises:

  • (ArgumentError)

    if neither or both of callable and block are provided, or if the preprocessor cannot be called with a single argument



337
338
339
340
341
342
343
344
345
346
# File 'lib/patient_http/configuration.rb', line 337

def register_preprocessor(name, callable = nil, &block)
  preprocessor = resolve_callable(:preprocessor, callable, &block)
  raise ArgumentError.new("register_preprocessor requires a callable or a block") if preprocessor.nil?

  validate_preprocessor_parameters!(preprocessor)

  @mutex.synchronize do
    @preprocessors = @preprocessors.merge(name.to_s => preprocessor)
  end
end

#register_secret(name, value = nil) {|name| ... } ⇒ void

This method returns an undefined value.

Register a named secret whose value can be referenced indirectly when building requests via PatientHttp.secret.

The value can be provided directly or as a block (callable). A block is invoked lazily with the secret name each time the secret is resolved, which is useful for values that should be read on demand (for example, from the environment).

Parameters:

  • name (String, Symbol)

    the secret name

  • value (Object, nil) (defaults to: nil)

    the secret value (omit when providing a block)

Yields:

  • (name)

    a block that returns the secret value (omit when providing a value)

Raises:

  • (ArgumentError)

    if neither or both of value and block are provided



304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/patient_http/configuration.rb', line 304

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

  @mutex.synchronize do
    @secrets[name.to_s] = block || value
    @secret_manager = SecretManager.new(secrets: @secrets.dup)
  end
end

#to_hHash

Convert to hash for inspection

Returns:

  • (Hash)

    hash representation with string keys



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/patient_http/configuration.rb', line 418

def to_h
  {
    "max_connections" => max_connections,
    "request_timeout" => request_timeout,
    "shutdown_timeout" => shutdown_timeout,
    "logger" => logger,
    "max_response_size" => max_response_size,
    "user_agent" => user_agent,
    "raise_error_responses" => raise_error_responses,
    "max_redirects" => max_redirects,
    "connection_pool_size" => connection_pool_size,
    "connection_timeout" => connection_timeout,
    "proxy_url" => proxy_url,
    "retries" => retries,
    "protocol" => protocol,
    "max_connections_per_host" => max_connections_per_host,
    "completion_threads" => completion_threads,
    "completion_retries" => completion_retries,
    "payload_stores" => payload_stores.keys,
    "default_payload_store" => default_payload_store_name,
    "secrets" => @mutex.synchronize { @secrets.keys },
    "preprocessors" => @mutex.synchronize { @preprocessors.keys }
  }
end