Skip to content

Fix streamed response bodies being consumed when recording - #11

Merged
craigpotter merged 1 commit into
mainfrom
fix/do-not-consume-non-seekable-response-streams
Jul 14, 2026
Merged

Fix streamed response bodies being consumed when recording#11
craigpotter merged 1 commit into
mainfrom
fix/do-not-consume-non-seekable-response-streams

Conversation

@craigpotter

Copy link
Copy Markdown
Collaborator

The problem

When a Saloon request is sent with Guzzle's stream => true config, the response body is backed by a non-seekable socket stream that can only be read once.

Barstool's onResponse middleware calls $response->body() to record the body. Saloon's Response::body() / Response::stream() only rewind seekable streams, so for a streamed response the recording drains the stream to EOF and it can never be rewound. By the time the application reads $response->body() or $response->stream(), the body is empty.

In practice this silently corrupts downstream data — e.g. writing $response->stream() to a filesystem produces a blank file whenever Barstool is enabled.

The fix

getResponseBody() now checks isSeekable() on the PSR stream before reading anything (the check itself doesn't consume the stream). Non-seekable bodies are recorded as <Streamed Body>, matching how streamed request bodies are already handled via StreamBodyRepository.

While in there, the content-type check now runs before the body is read, so unsupported bodies (large binaries etc.) are no longer buffered into memory just to be discarded as <Unsupported Barstool Response Content>.

Testing

Added a regression test that records a response backed by a NoSeekStream and asserts:

  • the recording stores <Streamed Body> alongside the usual status/headers metadata
  • the stream is still fully readable by the application afterwards

The new test fails on main (the stream comes back empty) and passes with this change.

When a response is backed by a non-seekable stream (e.g. Guzzle's
`stream => true` config), reading the body while recording drains the
stream to EOF and it cannot be rewound. The application then receives an
empty body from `$response->body()` or `$response->stream()`.

Barstool now records `<Streamed Body>` for non-seekable response bodies
without touching the stream, matching how streamed request bodies are
already handled. The content-type check also now runs before the body is
read, so unsupported bodies are no longer buffered just to be discarded.
@craigpotter
craigpotter merged commit 8d79958 into main Jul 14, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant