Skip to content

Commit

Permalink
allow Edge Functions to stream a compressed fetch response (#39608)
Browse files Browse the repository at this point in the history
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<Uint8Array>`, 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`
  • Loading branch information
Schniz committed Aug 21, 2022
1 parent bf82a47 commit 41d4aa0
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 9 deletions.
12 changes: 11 additions & 1 deletion packages/next/server/web/sandbox/sandbox.ts
Expand Up @@ -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[]
Expand Down Expand Up @@ -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()
}
Expand Down
Expand Up @@ -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)
},
],
])
Expand Down

0 comments on commit 41d4aa0

Please sign in to comment.