Skip to content

Commit c551a32

Browse files
author
Keen Yee Liau
authored
fix: image optimizer hangs when invalid image is requested (#33719)
When an invalid image is requested, the 'finish' event is never triggered, which ultimately leads to a 504 Gateway Timeout error. This is fixed by invoking the 'callback' in `_write()`. fix #33441 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent 311f66e commit c551a32

2 files changed

Lines changed: 18 additions & 3 deletions

File tree

packages/next/server/image-optimizer.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,17 @@ export async function imageOptimizer(
253253
mockRes.write = (chunk: Buffer | string) => {
254254
resBuffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
255255
}
256-
mockRes._write = (chunk: Buffer | string) => {
256+
mockRes._write = (
257+
chunk: Buffer | string,
258+
_encoding: string,
259+
callback: () => void
260+
) => {
257261
mockRes.write(chunk)
262+
// According to Node.js documentation, the callback MUST be invoked to signal that
263+
// the write completed successfully. If this callback is not invoked, the 'finish' event
264+
// will not be emitted.
265+
// https://nodejs.org/docs/latest-v16.x/api/stream.html#writable_writechunk-encoding-callback
266+
callback()
258267
}
259268

260269
const mockHeaders: Record<string, string | string[]> = {}
@@ -290,7 +299,6 @@ export async function imageOptimizer(
290299
await handleRequest(mockReq, mockRes, nodeUrl.parse(href, true))
291300
await isStreamFinished
292301
res.statusCode = mockRes.statusCode
293-
294302
upstreamBuffer = Buffer.concat(resBuffers)
295303
upstreamType =
296304
detectContentType(upstreamBuffer) || mockRes.getHeader('Content-Type')
@@ -328,7 +336,6 @@ export async function imageOptimizer(
328336
)
329337
return { finished: true }
330338
}
331-
332339
if (!upstreamType.startsWith('image/')) {
333340
res.statusCode = 400
334341
res.end("The requested resource isn't a valid image.")

test/integration/image-optimizer/test/index.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,14 @@ function runTests({
779779
expect(await res.text()).toBe("The requested resource isn't a valid image.")
780780
})
781781

782+
it('should error if the image file does not exist', async () => {
783+
const query = { url: '/does_not_exist.jpg', w, q: 80 }
784+
const opts = { headers: { accept: 'image/webp' } }
785+
const res = await fetchViaHTTP(appPort, '/_next/image', query, opts)
786+
expect(res.status).toBe(400)
787+
expect(await res.text()).toBe("The requested resource isn't a valid image.")
788+
})
789+
782790
it('should handle concurrent requests', async () => {
783791
await fs.remove(imagesDir)
784792
const query = { url: '/test.png', w, q: 80 }

0 commit comments

Comments
 (0)