Skip to content

Commit

Permalink
Change Flight response content type to application/octet-stream (#40665)
Browse files Browse the repository at this point in the history
Ensures Flight responses are not loaded as HTML.


## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a 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 a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
timneutkens committed Sep 18, 2022
1 parent 1bf7d4d commit d41ca43
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 6 deletions.
13 changes: 11 additions & 2 deletions packages/next/server/app-render.tsx
Expand Up @@ -51,6 +51,15 @@ export type RenderOptsPartial = {

export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial

/**
* Flight Response is always set to application/octet-stream to ensure it does not
*/
class FlightRenderResult extends RenderResult {
constructor(response: string | ReadableStream<Uint8Array>) {
super(response, { contentType: 'application/octet-stream' })
}
}

/**
* Interop between "export default" and "module.exports".
*/
Expand Down Expand Up @@ -500,7 +509,7 @@ export async function renderToHTMLOrFlight(

// Empty so that the client-side router will do a full page navigation.
const flightData: FlightData = pathname + (search ? `?${search}` : '')
return new RenderResult(
return new FlightRenderResult(
renderToReadableStream(flightData, serverComponentManifest).pipeThrough(
createBufferedTransformStream()
)
Expand Down Expand Up @@ -1054,7 +1063,7 @@ export async function renderToHTMLOrFlight(
).slice(1),
]

return new RenderResult(
return new FlightRenderResult(
renderToReadableStream(flightData, serverComponentManifest, {
context: serverContexts,
}).pipeThrough(createBufferedTransformStream())
Expand Down
15 changes: 13 additions & 2 deletions packages/next/server/render-result.ts
@@ -1,10 +1,21 @@
import type { ServerResponse } from 'http'

type ContentTypeOption = string | undefined

export default class RenderResult {
_result: string | ReadableStream<Uint8Array>
private _result: string | ReadableStream<Uint8Array>
private _contentType: ContentTypeOption

constructor(response: string | ReadableStream<Uint8Array>) {
constructor(
response: string | ReadableStream<Uint8Array>,
{ contentType }: { contentType?: ContentTypeOption } = {}
) {
this._result = response
this._contentType = contentType
}

contentType(): ContentTypeOption {
return this._contentType
}

toUnchunkedString(): string {
Expand Down
8 changes: 7 additions & 1 deletion packages/next/server/send-payload/index.ts
Expand Up @@ -71,10 +71,16 @@ export async function sendRenderResult({
}
}

const resultContentType = result.contentType()

if (!res.getHeader('Content-Type')) {
res.setHeader(
'Content-Type',
type === 'json' ? 'application/json' : 'text/html; charset=utf-8'
resultContentType
? resultContentType
: type === 'json'
? 'application/json'
: 'text/html; charset=utf-8'
)
}

Expand Down
6 changes: 5 additions & 1 deletion packages/next/server/web-server.ts
Expand Up @@ -370,10 +370,14 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
if (options.poweredByHeader && options.type === 'html') {
res.setHeader('X-Powered-By', 'Next.js')
}
const resultContentType = options.result.contentType()

if (!res.getHeader('Content-Type')) {
res.setHeader(
'Content-Type',
options.type === 'json'
resultContentType
? resultContentType
: options.type === 'json'
? 'application/json'
: 'text/html; charset=utf-8'
)
Expand Down
13 changes: 13 additions & 0 deletions test/e2e/app-dir/index.test.ts
Expand Up @@ -44,6 +44,19 @@ describe('app dir', () => {
})
afterAll(() => next.destroy())

it('should use application/octet-stream for flight', async () => {
const res = await fetchViaHTTP(
next.url,
'/dashboard/deployments/123?__flight__'
)
expect(res.headers.get('Content-Type')).toBe('application/octet-stream')
})

it('should use application/octet-stream for flight with edge runtime', async () => {
const res = await fetchViaHTTP(next.url, '/dashboard?__flight__')
expect(res.headers.get('Content-Type')).toBe('application/octet-stream')
})

it('should pass props from getServerSideProps in root layout', async () => {
const html = await renderViaHTTP(next.url, '/dashboard')
const $ = cheerio.load(html)
Expand Down

0 comments on commit d41ca43

Please sign in to comment.