fix: image optimizer fallback stream reuse#513
Conversation
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 855211d229
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
/bigbonk review |
There was a problem hiding this comment.
Solid fix. The approach — try the passthrough first, catch the TypeError from new Response(lockedStream), then refetch — is the right tradeoff. It keeps the happy path streaming with zero overhead, and only pays the cost of a second fetch when the transform actually consumed the body before failing.
The createPassthroughImageResponse dedup is clean, the unicode redirect fix is correct (URL.pathname returns percent-encoded paths per WHATWG), and the test coverage for the new fallback paths is thorough.
One minor nit and one question below, but neither are blocking.
| return new Response("The requested resource is not an allowed image type", { status: 400 }); | ||
| } | ||
|
|
||
| return createPassthroughImageResponse(refetchedSource, imageConfig); |
There was a problem hiding this comment.
Nit: if handlers.fetchAsset throws during the refetch (network error, DNS failure, etc.), the error propagates unhandled out of handleImageOptimization. This is arguably fine — there's not much else to do — but it would be a 500 instead of a graceful error response. Consider wrapping the entire catch body for consistency:
| return createPassthroughImageResponse(refetchedSource, imageConfig); | |
| return createPassthroughImageResponse(refetchedSource, imageConfig); | |
| } catch { | |
| return new Response("Image not found", { status: 404 }); | |
| } |
Or alternatively wrap just the fetchAsset call. Up to you — the current behavior is acceptable.
| setImageSecurityHeaders(headers, imageConfig); | ||
| return new Response(source.body, { status: 200, headers }); | ||
| try { | ||
| return createPassthroughImageResponse(source, imageConfig); |
There was a problem hiding this comment.
Worth noting for future readers: the try here catches the synchronous TypeError thrown by new Response(disturbed_body) inside createPassthroughImageResponse. Per the Fetch spec, the Response constructor's extract algorithm throws synchronously if the body stream is disturbed or locked. This is well-tested by the "refetches the source when transform consumes the stream" test case. A brief comment here would help future maintainers understand the control flow at a glance:
| return createPassthroughImageResponse(source, imageConfig); | |
| // createPassthroughImageResponse throws a TypeError if source.body was | |
| // consumed by a failed transformImage call (locked/disturbed ReadableStream). | |
| try { | |
| return createPassthroughImageResponse(source, imageConfig); |
| const tag = match[0]; | ||
| if (!tag.includes(`alt="${alt}"`)) continue; | ||
| const srcMatch = tag.match(/\ssrc="([^"]+)"/); | ||
| if (srcMatch) return srcMatch[1].replaceAll("&", "&"); |
There was a problem hiding this comment.
Minor: the & replacement here handles HTML entity encoding from the rendered markup, but & in the regex means literal & in the source. If the HTML is raw (not entity-encoded), this replaceAll is a no-op, which is fine. Just confirming this is intentional for robustness rather than a specific observed behavior?
|
Review submitted. Here's a summary of my feedback: Approved with minor suggestions:
The Codex review comment about |
Fix
/_vinext/imagefallback behavior when image transformation fails after consuming the source stream.This also fixes a Pages Router dev-mode parity bug where local image redirects did not percent-encode unicode paths in the
Locationheader.Root cause
The image optimization path on Workers streams the source image into the transform handler. If the transform implementation starts consuming the source stream and then throws, the original response body can no longer be reused safely for fallback.
Fix
This keeps the normal success path streaming and avoids tee-based buffering, while still recovering correctly from consumed-stream failures.
Changes
Locationheaders for unicode pathsTesting
pnpm test tests/shims.test.ts -t "handleImageOptimization"pnpm test tests/image-optimization-parity.test.tspnpm run fmtpnpm run typecheck