Grounded in a production incident · media-svc · both files read verbatim
← L1: io.Pipe ·
← L2: Reserved path ·
← L3: Streaming fix ·
Reference →
Both functions do the same thing: stream a provider video URL into a multipart POST without buffering. They diverge because their upload targets are completely different servers with different protocols, auth schemes, and response shapes.
| Factor | DownloadAndUploadFile | DownloadAndUploadToReservedURL |
|---|---|---|
| Upload target | Our own upload-svc | Google Cloud Storage (pre-signed POST URL) |
| Auth | Bearer token in Authorization header | None — auth is baked into the pre-signed URL itself |
| HTTP client | httpkit/request/v2.Post() wrapper | Bare http.NewRequestWithContext + http.DefaultClient.Do() |
| Form fields source | req.buildFormData() — deterministic ordered slice | req.FormFields — map[string]string, random iteration order |
| MIME type source | req.File.MimeType, default application/octet-stream | req.FormFields["content-type"] overrides req.File.MimeType |
| Filename source | req.File.Filename (caller provides it) | path.Base(req.FormFields["key"]) (derived from GCS object key) |
| LimitReader cap | limit — checks n == limit | limit+1 — checks n > limit |
| Response handling | Full JSON parsing → returns UploadResponse | Discard body → returns bare error |
| Return type | (UploadResponse, error) | error |
uploadResp, err := request.Post(ctx, r.endpoint, pr,
request.WithHeader(request.Header{
"Content-Type": contentType,
"Authorization": "Bearer " + authKey,
}),
)
httpReq, err := http.NewRequestWithContext(
ctx, http.MethodPost, req.UploadURL, pr)
httpReq.Header.Set("Content-Type", contentType)
// No Authorization header — auth is in the URL
resp, err := http.DefaultClient.Do(httpReq)
Why httpkit for upload-svc? Our internal service expects
Authorization: Bearer <key> — the httpkit wrapper provides
WithHeader, OTel tracing, and structured logging for free.
Why bare stdlib for GCS? A GCS pre-signed URL encodes the authorization
inside the query string (x-goog-signature=…). The httpkit wrapper unconditionally
injects OTel tracing headers (traceparent, tracestate) which are
irrelevant to GCS. Bare http.DefaultClient sends exactly and only what we specify.
Authorization header "would conflict with GCS's own auth validation." That is
wrong. httpkit never auto-adds Authorization (verified in
the wrapper source handleOptions — it only sets headers the caller passes via
WithHeader), and GCS simply ignores unknown headers like traceparent.
The real reason for bare stdlib is minimal, explicit headers with no framework-injected
OTel noise — not conflict avoidance.
for _, form := range req.buildFormData() { mw.WriteField(form.Key, form.Value) } // Returns an ordered []FormData slice // Fields: account_id, apply_thumbnail, etc.
for key, val := range req.FormFields { mw.WriteField(key, val) } // map[string]string — random iteration // Fields: key, policy, x-goog-*, content-type
file part is last. Our goroutine guarantees that
by writing all form fields in the loop and then writing the file part after the loop.
buildFormData() returns a slice because upload-svc's field names are
compiled into the code (account_id, apply_thumbnail, etc.). An
ordered slice is the natural representation. FormFields is a map because the
GCS policy fields come from the caller at runtime — we don't know in advance which
fields will be present, only that they came from a signed URL generator.
In the reserved path, the GCS signed policy includes a
{"content-type": "video/mp4"} condition. GCS validates that the
Content-Type header of the file part matches this condition exactly.
The override:
mimeType := req.File.MimeType // caller hint (fallback) if ct := req.FormFields["content-type"]; ct != "" { mimeType = ct // always wins — matches the signed policy }
If the caller passed MimeType: "video/mp4" and the policy also says
"video/mp4", the override is a no-op. If they ever differ,
FormFields["content-type"] is authoritative because it's what GCS
expects. Without this override, a mismatch would cause GCS to return HTTP 400
with a policy condition failure.
filename := path.Base(req.FormFields["key"]) // "upload/2026/06/17/01KV9X8G86DAERW5E7GJ9YHHG6.mp4" // → path.Base → "01KV9X8G86DAERW5E7GJ9YHHG6.mp4" if filename == "" || filename == "." { filename = req.File.Filename // fallback only }
The key field in FormFields is the GCS object path that was
signed when the pre-signed URL was generated. It always carries the correct filename
including extension (.mp4 for video, .png for image). If we
used req.File.Filename directly, the caller would have to know and supply
the exact filename. Instead, the GCS key is the single source of truth.
This is a subtle correctness fix applied in the reserved path that was not backfilled to the standard path.
n, err := io.Copy(fw, io.LimitReader(
dlResp.Body, limit)) // reads up to limit
if n == limit { // ← ambiguous!
pw.CloseWithError(...)
}
// A file EXACTLY limit bytes long
// will be falsely rejected here.
n, copyErr := io.Copy(fw, io.LimitReader(
dlResp.Body, limit+1)) // reads one extra byte
if n > limit { // ← unambiguous
pw.CloseWithError(...)
}
// n > limit only if file was truly larger.
// n == limit means "exactly limit bytes" → OK.
limit+1 work?
io.LimitReader(r, n) reads at most n bytes from
r. With limit+1: if the file is exactly limit
bytes, the reader reads all limit bytes and then hits EOF naturally —
n == limit, no error. If the file is larger, the reader reads
limit+1 bytes and stops — n > limit, we reject. The one
extra byte is never written to the pipe (the n > limit check runs before
mw.Close()), so no corrupt data reaches GCS.
if !uploadResp.Ok() {
body, _ := io.ReadAll(
io.LimitReader(uploadResp.Body, 4096))
var errResp UploadErrorResponse
json.Unmarshal(body, &errResp)
// Returns structured apierror with error code
}
var result UploadResponse
uploadResp.JSON(&result)
return result, nil // URL, thumbnail, duration
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(
io.LimitReader(resp.Body, 4096))
// No JSON parsing — GCS returns XML on error
log.Error(...)
return apierror.Error("0500", ...)
}
io.Copy(io.Discard, resp.Body)
return nil // no useful metadata returned
upload-svc returns a JSON body with the CDN URL, thumbnail info, and video duration — data the caller needs to store in the database. The response is worth parsing.
By default GCS pre-signed POST returns HTTP 204 with an empty body on success. On failure
it returns XML (not JSON). The resultURL was already known before the
upload started — it's reserved.ResultURL, which was part of the original
reserved upload slot. There is nothing useful to parse.
success_action_status form field:
absent → 204 (empty), =200 → 200 (empty),
=201 → 201 with an XML metadata body (Bucket, Key, ETag, Location).
Our io.Copy(io.Discard, ...) discards the body regardless. It is correct
today only because our live FormFields never include
success_action_status, so GCS defaults to 204. If the URL generator ever adds
that field, we would silently discard returned metadata.
Q1. Why does the reserved path use http.DefaultClient.Do() instead of httpkit request.Post()?
Q2. The goroutine in the reserved path iterates req.FormFields as a map in random order. Why is this safe?
Q3. A 500 MB file is downloaded. limit is 200 MB. With limit+1 in the reserved path, what value does n hold after io.Copy?
Q4. Why does the reserved path derive the filename from path.Base(req.FormFields["key"]) instead of req.File.Filename?