From 41d4aa04c4b2e637e24a26646b7023d6aab4d98b Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Sun, 21 Aug 2022 13:43:02 +0300 Subject: [PATCH] allow Edge Functions to stream a compressed `fetch` response (#39608) When a user tried to have the following Edge Function: ```ts export default () => fetch("https://example.vercel.sh"); ``` The Edge Function were failing. Why is that? When `fetch` was called, an implicit `Accept-Encoding` header was added to allow the origin to return a compressed response. Then, the origin will set the `Content-Encoding` header in the response, to let the client know that the body needs to be decompressed in order to be read. That creates an issue though: `response.body` will be a `ReadableStream`, or, a stream that contains binary data that decodes into _the uncompressed data_ (or, plain text!). What it means, is that `response.body` is uncompressed data, while `response.headers.get('content-encoding')` is marking the response body as compressed payload. This confuses the HTTP clients and makes them fail. This commit removes the `content-encoding`, `transfer-encoding` and `content-length` headers from the response, as the Next.js server _always_ streams Edge Function responses. ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` --- packages/next/server/web/sandbox/sandbox.ts | 12 +++++++++++- .../app/pages/api/edge.js | 10 ++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/next/server/web/sandbox/sandbox.ts b/packages/next/server/web/sandbox/sandbox.ts index 947d0d6132af673..773936c75017435 100644 --- a/packages/next/server/web/sandbox/sandbox.ts +++ b/packages/next/server/web/sandbox/sandbox.ts @@ -6,6 +6,12 @@ import { requestToBodyStream } from '../../body-streams' export const ErrorSource = Symbol('SandboxError') +const FORBIDDEN_HEADERS = [ + 'content-length', + 'content-encoding', + 'transfer-encoding', +] + type RunnerFn = (params: { name: string env: string[] @@ -74,12 +80,16 @@ export const run = withTaggedErrors(async (params) => { : undefined try { - return await edgeFunction({ + const result = await edgeFunction({ request: { ...params.request, body: cloned && requestToBodyStream(runtime.context, cloned), }, }) + for (const headerName of FORBIDDEN_HEADERS) { + result.response.headers.delete(headerName) + } + return result } finally { await params.request.body?.finalize() } diff --git a/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js b/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js index dd24197003cd826..49da52df583bda7 100644 --- a/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js +++ b/test/e2e/edge-compiler-can-import-blob-assets/app/pages/api/edge.js @@ -38,20 +38,14 @@ const handlers = new Map([ 'remote-full', async () => { const url = new URL('https://example.vercel.sh') - const response = await fetch(url) - const headers = new Headers(response.headers) - headers.delete('content-encoding') - return new Response(response.body, { headers, status: response.status }) + return fetch(url) }, ], [ 'remote-with-base', async () => { const url = new URL('/', 'https://example.vercel.sh') - const response = await fetch(url) - const headers = new Headers(response.headers) - headers.delete('content-encoding') - return new Response(response.body, { headers, status: response.status }) + return fetch(url) }, ], ])