Class: Axn::Webhooks::Outbound::Deliver

Inherits:
Object
  • Object
show all
Includes:
Axn, VendorFacet
Defined in:
lib/axn/webhooks/outbound/deliver.rb

Overview

A single delivery attempt + the self-managed retry engine. Built as an Axn: metrics/OTel/ structured logs per attempt come free. Retryable responses reschedule via axn's adapter-agnostic call_async(_async: { wait: }) seam (never branching on adapter type); unexpected exceptions propagate so the async adapter retries the un-acked job (at-least-once).

Constant Summary collapse

MANAGED_HEADERS =

The headers Deliver adds AFTER the signer's, and therefore the ones a signer must not emit: Ruby Hash keys are case-sensitive so a differently-cased duplicate survives the merge below, but Net::HTTP is case-INSENSITIVE and the later assignment wins — silently replacing the signature. Signer::HmacSigner rejects these at declaration time.

%w[content-type user-agent].freeze
PLAUSIBLE_FIELD_NAME =

A plausible field-name (Authorization, content_type -- the documented common case of writing a headers resolver with a Symbol/String literal key) is a short, simple identifier -- used by key_desc to decide whether a malformed header's key is safe to log as-is (see add_custom_header).

/\A[A-Za-z_][A-Za-z0-9_]{0,49}\z/
FORBIDDEN_HEADER_VALUE_BYTES =

RFC 7230's field-value grammar forbids every control byte except HTAB (0x09) -- CR/LF (0x0D/0x0A) are the ones Net::HTTP itself raises on, but any OTHER control byte (NUL, BEL, ...) is equally invalid on the wire and unvalidated here would reach the receiver (see add_custom_header).

/[\x00-\x08\x0A-\x1F\x7F]/

Instance Method Summary collapse

Methods included from VendorFacet

included

Instance Method Details

#callObject



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/axn/webhooks/outbound/deliver.rb', line 71

def call
  # Scoped deliberately to ONLY `post` (not the whole method): a network error talking to
  # the receiver is a retryable delivery failure, but if `retry_or_exhaust!`'s own
  # `call_async` raises while ENQUEUING the follow-up job (e.g. a Redis/Sidekiq outage),
  # that must propagate as a loud exception — not get caught here and misinterpreted as
  # another delivery network error, which would re-run retry_or_exhaust! a second time in
  # the same attempt (a duplicate enqueue). Letting it propagate means the current job goes
  # un-acked and the async adapter's own retry path handles the outage (at-least-once).
  response = nil
  begin
    response = post
  rescue *Transport::RETRYABLE_NETWORK_ERRORS => e
    return retry_or_exhaust!(network_error: e)
  end

  return if success?(response.status) # 2xx -> done
  return retry_or_exhaust!(retry_after: header_value(response.headers, "retry-after")) if retryable?(response.status)

  fail!(permanent_failure_message(response))
end