From f7291d105e1d605df93ab61bb0048458bffedab5 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 5 Jul 2023 11:05:08 +0200 Subject: [PATCH] EF: Don't crash CLI when uncompressed request throws uncaught exception (#5837) * chore: add test * fix: add content negotiation to error decoding --- src/utils/proxy.mjs | 24 +++++++++++++++-- tests/integration/100.command.dev.test.cjs | 30 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/utils/proxy.mjs b/src/utils/proxy.mjs index 6bed105aa62..1c52551511b 100644 --- a/src/utils/proxy.mjs +++ b/src/utils/proxy.mjs @@ -36,9 +36,29 @@ import { generateRequestID } from './request-id.mjs' import { createRewriter, onChanges } from './rules-proxy.mjs' import { signRedirect } from './sign-redirect.mjs' -const decompress = util.promisify(zlib.gunzip) +const gunzip = util.promisify(zlib.gunzip) +const brotliDecompress = util.promisify(zlib.brotliDecompress) +const deflate = util.promisify(zlib.deflate) const shouldGenerateETag = Symbol('Internal: response should generate ETag') +/** + * @param {Buffer} body + * @param {string | undefined} contentEncoding + * @returns {Promise} + */ +const decompressResponseBody = async function (body, contentEncoding = '') { + switch (contentEncoding) { + case 'gzip': + return await gunzip(body) + case 'br': + return await brotliDecompress(body) + case 'deflate': + return await deflate(body) + default: + return body + } +} + const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => { const { error: { message, name, stack }, @@ -479,7 +499,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port, if (isEdgeFunctionsRequest(req) && isUncaughtError) { const acceptsHtml = req.headers && req.headers.accept && req.headers.accept.includes('text/html') - const decompressedBody = await decompress(responseBody) + const decompressedBody = await decompressResponseBody(responseBody, req.headers['content-encoding']) const formattedBody = formatEdgeFunctionError(decompressedBody, acceptsHtml) const errorResponse = acceptsHtml ? await renderErrorTemplate(formattedBody, './templates/function-error.html', 'edge function') diff --git a/tests/integration/100.command.dev.test.cjs b/tests/integration/100.command.dev.test.cjs index f70e5873415..0752c829039 100644 --- a/tests/integration/100.command.dev.test.cjs +++ b/tests/integration/100.command.dev.test.cjs @@ -553,6 +553,36 @@ test('When an edge function fails, serves a fallback defined by its `on_error` m }) }) +test('When an edge function throws uncaught exception, the dev server continues working', async (t) => { + await withSiteBuilder('site-with-edge-function-uncaught-exception', async (builder) => { + builder + .withNetlifyToml({ + config: { + build: { + edge_functions: 'netlify/edge-functions', + }, + }, + }) + .withEdgeFunction({ + config: { path: '/hello' }, + handler: () => { + const url = new URL('/shouldve-provided-a-base') + return new Response(url.toString()) + }, + name: 'hello-1', + }) + + await builder.buildAsync() + + await withDevServer({ cwd: builder.directory }, async (server) => { + const response1 = await got(`${server.url}/hello`, { + decompress: false, + }) + t.is(response1.statusCode, 200) + }) + }) +}) + test('redirect with country cookie', async (t) => { await withSiteBuilder('site-with-country-cookie', async (builder) => { builder