Skip to main content

Safer HTTP Client Runtime

v2.5.2 hardens the HTTP client around interrupted streams, retries, telemetry, and deterministic LLM-backed testing. The release keeps the common HTTP API focused while making failure and replay behavior more explicit.

Transport and Streaming Correctness

  • cURL streaming now checks completed transfer results from curl_multi_info_read(); dropped connections are no longer reported as successfully completed streams.
  • Redirect response headers no longer leak or merge values across response hops.
  • Guzzle, Symfony, and cURL transport failures are normalized more consistently into connection, timeout, network, and HTTP request exceptions.
  • Stream completion is reported only after natural iterator exhaustion. Interrupted ArrayStream, IterableStream, SSE, and decorated streams remain incomplete.
  • Raw iterables passed to HttpResponse are rejected instead of being silently buffered. Use streamingFromIterable() or bufferedFromIterable() explicitly.
  • SSE parsing emits a final unterminated event and enforces a bounded parser buffer.

Safer Retries and Idempotency

  • Retry-After delays are capped by maxDelayMs, preventing a server response from blocking a PHP worker for hours.
  • Retries are limited to idempotent methods by default. POST and PATCH require an explicit opt-in and should be paired with IdempotencyMiddleware where supported.
  • Generated idempotency keys remain stable across retry attempts and middleware ordering. Header matching is case-insensitive.
  • Numeric and RFC 7231 Retry-After values are parsed defensively, including invalid, negative, fractional, and oversized values.

Privacy-Safe Diagnostics and Telemetry

HTTP driver events and telemetry no longer attach complete request or synchronous response bodies by default. They retain safe URLs, redacted headers, status data, and body-size metadata instead. Optional streaming telemetry capture is bounded and redacts captured content. Common credentials in URLs, authorization headers, cookies, response metadata, and known body fields are sanitized before record/replay persistence. Prompts, model outputs, PII, and provider-specific secrets may still be present in fixtures and must be reviewed before sharing.

Versioned HTTP Record/Replay

The HTTP client now exposes an immutable cassette API through RecordReplayMiddleware::recordTo('/tmp/http-cassette') and RecordReplayMiddleware::replayFrom('/tmp/http-cassette'). The cassette store provides:
  • versioned, atomic filesystem persistence with payload bytes outside metadata JSON;
  • strict ordered sessions for repeated and interleaved requests;
  • hermetic replay by default, with live passthrough requiring explicit policy;
  • typed missing, mismatch, exhausted, corrupt, and unsupported-version failures;
  • canonical JSON request matching while preserving array order and scalar types;
  • binary-safe, lazy, one-shot streamed replay without loading all chunks into a PHP array;
  • boundary-safe credential sanitization for streamed payloads; and
  • an isolated compatibility reader for existing pre-versioned one-file recordings.
Existing RecordingMiddleware and ReplayMiddleware integrations remain available as deprecated compatibility surfaces. New integrations should use recordTo(), replayFrom(), recordWith(), or replayWith().

Deterministic Example Verification

The shared examples/boot.php integration supports pass, record, and hermetic replay modes without editing each example. Use just examples-record getters_and_setters, just examples-replay getters_and_setters, or just examples-replay-all to run selected examples or the corpus. Replay provisions dummy provider keys only for configuration resolution and serves recorded responses without contacting the provider. The new examples-qa skill documents how to interpret hub results: ASSERT and ERROR are real failures, live-only FLAKY results are tolerated but replay failures are not, and skipped broken/no-replay examples are not evidence of a pass.

Upgrade Notes

  1. Replace direct mutable RecordReplayMiddleware construction and setters with recordTo() or replayFrom(). Use recordWith()/replayWith() for custom stores.
  2. Replay now fails closed for missing interactions. If live fallback is genuinely required, opt into ReplayMissPolicy::Passthrough explicitly.
  3. Existing pre-versioned recordings are read through the compatibility adapter. New recordings use the versioned cassette layout; refresh fixtures when convenient.
  4. Review retry behavior for POST and PATCH: they are no longer retried by default. Opt in only when the endpoint’s idempotency contract is understood.
  5. Replace raw iterable response construction with HttpResponse::streamingFromIterable() or HttpResponse::bufferedFromIterable().
  6. Treat cassettes and diagnostic output as sensitive application data even after automatic credential sanitization.