Files
tech-log-backend/docs/httpclient/streaming.md
T
DongHyeonkaandClaude Opus 5 5f10b791d3 chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.

Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:48:43 +09:00

53 lines
2.3 KiB
Markdown

# Streaming and Large Bodies
## Response lifecycle
A blocking streaming download returns `BlockingStreamingResponse`, never a bare `InputStream`.
Closing is idempotent and always releases the connection — after a full read, a partial read, a
decode failure, or a size rejection. The status is validated before any body byte is delivered, so a
failed download never becomes a half-consumed stream the caller has to reason about.
A reactive download emits bounded `DataBuffer` values. Buffers are released on completion, error,
and cancellation; a dropped buffer is direct memory nobody returns.
Wire bytes and decoded bytes are bounded independently, because a compressed payload passes a wire
check and then expands. Limits are enforced while reading, not after buffering.
## The first-byte boundary
```text
response headers received
→ nothing delivered yet
→ a read-only operation may still be retried
→ first InputStream read or first Flux onNext
→ transparent retry is permanently disabled
```
`FirstByteDeliveryGuard` latches once and never resets. Retrying after delivery would replay a
stream the caller has already partly consumed, producing duplicated or reordered data that no
downstream code can detect.
## Request bodies
A reopenable body is opened once per attempt, which is what makes it replayable; reusing the
previous stream would silently send an empty body on the retry. A one-shot stream or publisher
instance is never retried. `ReactiveBodySource` takes a publisher *factory* rather than a publisher
so a reactive body can honestly declare itself replayable.
A multipart body is exactly as replayable as its weakest part.
## Server-sent events
Three budgets stay separate:
- `setupDeadline` — establishing the stream
- `streamingIdleTimeout` — silence once it is open
- `maxStreamDuration` — optional total lifetime
Applying the request-shaped `total-call` timeout to an SSE subscription would terminate a perfectly
healthy stream on schedule, so it is not applied.
`Last-Event-ID` is opt-in. Replaying from an id is only correct when the producer guarantees it;
sending it blindly can skip or duplicate events. Reconnects consume the retry budget like any other
physical attempt, and cancelling the subscription stops both the stream and any pending reconnect.