From 08e09a5ca08cace5de173f806efdc5f5c0401ac3 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 26 Jan 2022 16:31:09 -0800 Subject: [PATCH] fix: image optimizer hangs when invalid image is requested 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 https://github.com/vercel/next.js/issues/33441 --- packages/next/server/image-optimizer.ts | 13 ++++++++++--- test/integration/image-optimizer/test/index.test.js | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index de5a6315731309a..7a0f7fe1ec1804d 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -253,8 +253,17 @@ export async function imageOptimizer( mockRes.write = (chunk: Buffer | string) => { resBuffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) } - mockRes._write = (chunk: Buffer | string) => { + mockRes._write = ( + chunk: Buffer | string, + _encoding: string, + callback: () => void + ) => { mockRes.write(chunk) + // According to Node.js documentation, the callback MUST be invoked to signal that + // the write completed successfully. If this callback is not invoked, the 'finish' event + // will not be emitted. + // https://nodejs.org/docs/latest-v16.x/api/stream.html#writable_writechunk-encoding-callback + callback() } const mockHeaders: Record = {} @@ -290,7 +299,6 @@ export async function imageOptimizer( await handleRequest(mockReq, mockRes, nodeUrl.parse(href, true)) await isStreamFinished res.statusCode = mockRes.statusCode - upstreamBuffer = Buffer.concat(resBuffers) upstreamType = detectContentType(upstreamBuffer) || mockRes.getHeader('Content-Type') @@ -328,7 +336,6 @@ export async function imageOptimizer( ) return { finished: true } } - if (!upstreamType.startsWith('image/')) { res.statusCode = 400 res.end("The requested resource isn't a valid image.") diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 89f9cd36d7fff31..26ea2bfc9d3a440 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -779,6 +779,14 @@ function runTests({ expect(await res.text()).toBe("The requested resource isn't a valid image.") }) + it('should error if the image file does not exist', async () => { + const query = { url: '/does_not_exist.jpg', w, q: 80 } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(appPort, '/_next/image', query, opts) + expect(res.status).toBe(400) + expect(await res.text()).toBe("The requested resource isn't a valid image.") + }) + it('should handle concurrent requests', async () => { await fs.remove(imagesDir) const query = { url: '/test.png', w, q: 80 }