From cc5345bba852077c332ddc0b75279a0f96d4321a Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 24 Feb 2022 13:43:22 +0100 Subject: [PATCH] Add proper headers to responses in web server (#34723) This PR adds the `X-Powered-By` and `Content-Type` headers to responses sent by the web server. The latter enables compression for the Edge runtime. Still, the web server doesn't have `Content-Length` and `ETag` as the response is usually dynamic. Part of #31506. ## Bug - [x] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have 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 helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- packages/next/server/next-server.ts | 9 ++++++++- packages/next/server/web-server.ts | 14 ++++++++++++++ .../test/index.test.js | 6 ++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index a19c4477d793..69d30e7357d4 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -9,6 +9,7 @@ import type { PrerenderManifest } from '../build' import type { Rewrite } from '../lib/load-custom-routes' import type { BaseNextRequest, BaseNextResponse } from './base-http' import type { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' +import type { PayloadOptions } from './send-payload' import { execOnce } from '../shared/lib/utils' import { @@ -43,7 +44,7 @@ import { route } from './router' import { run } from './web/sandbox' import { NodeNextRequest, NodeNextResponse } from './base-http/node' -import { PayloadOptions, sendRenderResult } from './send-payload' +import { sendRenderResult } from './send-payload' import { getExtension, serveStatic } from './serve-static' import { ParsedUrlQuery } from 'querystring' import { apiResolver } from './api-utils/node' @@ -584,6 +585,12 @@ export default class NextNodeServer extends BaseServer { protected streamResponseChunk(res: NodeNextResponse, chunk: any) { res.originalResponse.write(chunk) + + // When both compression and streaming are enabled, we need to explicitly + // flush the response to avoid it being buffered by gzip. + if (this.compression && 'flush' in res.originalResponse) { + ;(res.originalResponse as any).flush() + } } protected async imageOptimizer( diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index b3e11bc0bf4a..cb0c8b5eba47 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -148,6 +148,20 @@ export default class NextWebServer extends BaseServer { options?: PayloadOptions | undefined } ): Promise { + // Add necessary headers. + // @TODO: Share the isomorphic logic with server/send-payload.ts. + if (options.poweredByHeader && options.type === 'html') { + res.setHeader('X-Powered-By', 'Next.js') + } + if (!res.getHeader('Content-Type')) { + res.setHeader( + 'Content-Type', + options.type === 'json' + ? 'application/json' + : 'text/html; charset=utf-8' + ) + } + // @TODO const writer = res.transformStream.writable.getWriter() diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 79a161d66862..d93f233a1aab 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -177,6 +177,12 @@ describe('Edge runtime - dev', () => { expect(content).toMatchInlineSnapshot('"foo.client"') }) + it('should have content-type and content-encoding headers', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8') + expect(res.headers.get('content-encoding')).toBe('gzip') + }) + basic(context, { env: 'dev' }) streaming(context) rsc(context, { runtime: 'edge', env: 'dev' })