You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cache-proxy): forward inbound body framing transparently (#527)
This is the actual root cause of the upstream-S3 501 NotImplemented errors
on parquet writes that we've been chasing through three previous PRs.
forwardUncached built its outbound request via http.NewRequestWithContext
with the inbound r.Body as the body. Per Go's docs:
When body is of type *bytes.Buffer, *bytes.Reader, or *strings.Reader,
the returned request's ContentLength is set [...]. For other types,
the default is left as 0; the body is then sent using chunked
transfer encoding.
r.Body is a generic io.ReadCloser, so the outbound req had ContentLength=0
and Go's Transport fell back to Transfer-Encoding: chunked. AWS S3 returns
501 NotImplemented for chunked PUT/POST regardless of whether the client
intended chunked.
So even though DuckDB sent a perfectly clean Content-Length-bearing PUT,
the proxy was rewriting it as chunked and S3 rejected it. The chain of
prior PRs (#516 deadlock fix, #518 / #519 logging, #524 / #525 / #526
visibility) gave us the breadcrumbs to actually see this; the fix itself
is one line: mirror ContentLength + TransferEncoding + Trailer from the
inbound request so the wire shape is preserved.
Verified the chunked / 501 chain is impossible to recreate now: AWS
Sigv4 signs `host;x-amz-content-sha256;x-amz-date` (per
duckdb-httpfs/src/s3fs.cpp:84), so neither Content-Length nor
Transfer-Encoding is in the signed headers list — meaning we're free to
set Content-Length without invalidating the signature.
Tests cover the proxy-as-transparent-forwarder invariant for the
non-cached path:
- TestForwardUncachedPropagatesContentLength: regression — outbound
ContentLength matches inbound, no Transfer-Encoding: chunked. Verified
this fails on the pre-fix code (origin sees ContentLength=-1 and
Transfer-Encoding: chunked).
- TestForwardUncachedPreservesRequestHeaders: Authorization, x-amz-*,
custom headers round-trip to origin verbatim.
- TestForwardUncachedStripsHopByHopBothDirections: Connection /
Keep-Alive stripped per RFC 7230 in both request and response, while
non-hop-by-hop headers pass through.
- TestForwardUncachedPreservesQueryString: AWS multipart-upload params
(?uploads, ?partNumber, ?uploadId=...) round-trip exactly so Sigv4's
canonical-request hash is preserved.
- TestForwardUncachedPreservesResponseBodyBytewise / Non2xx: response
body bytes (binary, XML envelopes) are forwarded byte-for-byte. Locks
in that the log_preview capture on non-2xx doesn't corrupt the body.
- TestForwardUncachedPreservesMethod: PUT/POST/DELETE/HEAD all reach
the origin unchanged.
0 commit comments