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) ⇒ 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



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
# File 'lib/patient_http/configuration.rb', line 71

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
)
  @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
end

Instance Attribute Details

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



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

def connection_pool_size
  @connection_pool_size
end

#connection_timeoutNumeric?

Returns Connection timeout in seconds.

Returns:

  • (Numeric, nil)

    Connection timeout in seconds



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

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



365
366
367
# File 'lib/patient_http/configuration.rb', line 365

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



120
121
122
# File 'lib/patient_http/configuration.rb', line 120

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_redirectsInteger

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

Returns:

  • (Integer)

    Maximum number of redirects to follow (0 disables redirects)



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

def max_redirects
  @max_redirects
end

#max_response_sizeInteger

Returns Maximum response size in bytes.

Returns:

  • (Integer)

    Maximum response size in bytes



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

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



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

def protocol
  @protocol
end

#proxy_urlString?

Returns HTTP/HTTPS proxy URL (supports authentication).

Returns:

  • (String, nil)

    HTTP/HTTPS proxy URL (supports authentication)



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

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



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

def raise_error_responses
  @raise_error_responses
end

#request_timeoutNumeric

Returns Default request timeout in seconds.

Returns:

  • (Numeric)

    Default request timeout in seconds



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

def request_timeout
  @request_timeout
end

#retriesInteger

Returns Number of retries for failed requests.

Returns:

  • (Integer)

    Number of retries for failed requests



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

def retries
  @retries
end

#secret_managerSecretManager (readonly)

Returns the secret manager instance.

Returns:



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

def secret_manager
  @secret_manager
end

#shutdown_timeoutNumeric

Returns Graceful shutdown timeout in seconds.

Returns:

  • (Numeric)

    Graceful shutdown timeout in seconds



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

def shutdown_timeout
  @shutdown_timeout
end

#user_agentString?

Returns Default User-Agent header value.

Returns:

  • (String, nil)

    Default User-Agent header value



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

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



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

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



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

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

#encryption_key=(keys) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/patient_http/configuration.rb', line 211

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:



246
247
248
# File 'lib/patient_http/configuration.rb', line 246

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:



352
353
354
355
356
357
358
359
360
# File 'lib/patient_http/configuration.rb', line 352

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:



370
371
372
# File 'lib/patient_http/configuration.rb', line 370

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



310
311
312
# File 'lib/patient_http/configuration.rb', line 310

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



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/patient_http/configuration.rb', line 328

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



295
296
297
298
299
300
301
302
303
304
# File 'lib/patient_http/configuration.rb', line 295

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



262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/patient_http/configuration.rb', line 262

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



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/patient_http/configuration.rb', line 376

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,
    "payload_stores" => payload_stores.keys,
    "default_payload_store" => default_payload_store_name,
    "secrets" => @mutex.synchronize { @secrets.keys },
    "preprocessors" => @mutex.synchronize { @preprocessors.keys }
  }
end