Skip to content

Axum: reuse outbound clients and preserve proxy HTTP semantics #395

Open

Description

@ChristianPavilonis

Part of #391.

Description

Make the native proxy client suitable for repeated production requests. Reuse connection pools and define what the proxy preserves, rejects, or rewrites instead of inheriting accidental client defaults.

Current implementation on audited main

  • Every into_core_request constructs AxumProxyClient::try_new and a new ProxyHandle, including requests that never proxy; see request.rs, lines 55-L59. Pools therefore are not shared across incoming requests.
  • try_new builds reqwest with a fixed 30-second timeout; no adapter-configurable connect/total policy or explicit redirect policy is exposed; see proxy.rs, lines 14-L30.
  • All supplied request headers are copied. Response headers use HeaderMap::insert, which replaces earlier values for repeated names such as Set-Cookie; see proxy.rs, lines 40-L47 and proxy.rs, lines 66-L74.
  • Uploads and responses are buffered. Existing integration tests cover basic methods, headers, error statuses, and finite body round trips, but not repeated headers, redirect policy, pooling, or timeout cleanup.

Prerequisite PR and scope correction

PR #275 is the prerequisite and supersedes most of the original proposed fix. It deletes legacy proxy.rs/ProxyClient usage in favor of the portable outbound HTTP contracts and AxumOutboundClient. AxumServiceState initializes a shared Arcreqwest::Client and passes it to request conversion; see service.rs at the inspected PR head and request.rs at the inspected PR head.

The new transport explicitly disables redirects and automatic decoding; see outbound.rs at the inspected PR head. It adds typed timeout/content-coding policies and uses append when copying response fields. Its body modes and framing/header contracts already own streaming, repeated fields, and hop-by-hop normalization.

Do not build a second proxy API or reintroduce the fixed 30-second legacy client. Re-audit the accepted implementation, verify pooling and wire behavior through the final production path, and implement only gaps left in runtime configuration, startup failure, policy exposure, or contract coverage. This issue may finish mostly as acceptance evidence if the PR satisfies the checks.

Proposed solution

  1. Adopt PR feat: implement portable outbound HTTP and request lifecycle contracts #275's HttpClient/outbound contracts and shared native transport. Remove any expectation of modifying or restoring the retired proxy.rs seam.
  2. Verify connection reuse across incoming requests, repeated response fields, hop-by-hop filtering, authority/Host/SNI policy, redirects, content decoding, and streamed bodies through the final server path. Reuse existing PR contract tests and add only uncovered integration cases.
  3. Expose any missing production policy through the existing timeout, TLS, body-limit, cache, and authority contracts rather than an Axum-only competing API. Keep TLS verification enabled and retries explicit.
  4. Re-audit transport initialization failure. The inspected service stores try_transport().ok(); determine whether production should fail readiness/startup instead of deferring the failure to each admitted request, in coordination with Axum: define a production server lifecycle with bounded graceful shutdown #392 and Axum: make production configuration and local storage explicit #396.
  5. Verify category-only public failures and resource cleanup. Close this issue with evidence if the accepted PR already satisfies the wire/client requirements; do not invent new implementation work to keep the issue open.

Done when

  • Re-audit the accepted PR feat: implement portable outbound HTTP and request lifecycle contracts #275 implementation and record which checks are already covered. Add only residual changes and missing production acceptance evidence.
  • The accepted standard server path uses its shared outbound transport, and a local origin test proves connection reuse across incoming requests.
  • Two Set-Cookie response values survive the complete proxy path unchanged.
  • Tests cover hop-by-hop filtering, Connection-nominated headers, Host handling, and header-value preservation.
  • Redirect behavior is explicit and tested, including cross-origin redirects and sensitive headers.
  • Configured connect/request deadlines and connection failures return safe responses and release work.
  • Existing buffered-body and new streamed-body proxy tests pass.
  • Default TLS verification, destination-policy ownership, and retry behavior are documented.

Related work and boundaries

Coordinate with #79, #81, #82, and #85. Those issues own the shared dynamic-backend abstraction and portable backend options; reuse any implemented policy instead of creating an Axum-only competing API. Trusted incoming proxy metadata belongs to the diagnostics/security task.

Affected areas

Adapter -- Axum; Core (routing, extractors, middleware) only for necessary shared proxy policy; Documentation.

Dependencies

Primary prerequisite: PR #275. Build residual production work on its accepted contracts, not the superseded main implementation.

Depends on #394 for bounded body transport. Pool reuse and repeated-header fixes can land independently first.

Acceptance sequence and downstream coverage

After accepted PR #275, inventory existing wire/pool tests before adding residual changes. #398/#399 can prepare packaging first; their final-image outbound checks should reuse this issue's controlled-origin/TLS/header fixtures.

Trusted Server has a separate outbound implementation and deliberately request-scoped clients after a truncated-POST/Next.js regression. Its migration to the shared contracts must retain that regression coverage; a framework dependency bump alone does not prove downstream behavior. The application owns its migration tests. Do not reintroduce legacy proxy.rs or add broad retry policy to satisfy container CI.

Implementation and relationship tracking

No published implementation/acceptance PR currently represents this issue. PR #275 supplies the HTTP foundation. GitHub now records #394 as a blocker for bounded-body-transport acceptance; pooling and repeated-header work may proceed independently as described above.

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions