rsnext/test/e2e/app-dir/app-middleware.test.ts

125 lines
3.6 KiB
TypeScript
Raw Normal View History

Support overriding request headers in middlewares (#41380) This PR adds a feature in middleware to add, modify, or delete request headers. This feature is quite useful to pass data from middleware to Serverless/Edge API routes. ### Questions for Reviewers - Should we deny modifying standard request headers like `Transfer-Encoding`? - Should we throw an error if the header is too large? Real-world HTTP servers will accept up to only 8KB - 32KB. ### New APIs Adds a new option `request.headers` to the `MiddlewareResponseInit` parameter in `NextResponse.next()` and `NextResponse.rewrite()`. It's a [`Header`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object holding *all* request headers. Specifically: ```ts interface MiddlewareResponseInit extends ResponseInit { request?: { headers?: Headers } } ``` ### Example ```ts // pages/api/hello.ts export default (req, res) => { const valueFromMiddleware = req.headers['x-hello-from-middleware'] return res.send(valueFromMiddleware) } // middleware.ts import { NextRequest, NextResponse } from 'next/server' export default function middleware(request: NextRequest) { // Clone request headers const headers = new Headers(request.headers); // Add a new request header headers.set('x-hello-from-middleware', 'foo'); // Delete a request header from the client headers.delete('x-from-client'); const resp = NextResponse.next({ // New option `request.headers` which accepts a Headers object // overrides request headers with the specified new ones. request: { headers } }); // You can still set *response* headers to the client, as before. resp.headers.set('x-hello-client', 'bar'); return resp; } ``` ### New middleware headers - `x-middleware-override-headers`: A comma separated list of *all* request header names. Headers not listed will be deleted. - `x-middleware-request-<name>`: A new value for the header `<name>`. ## Related Discussions - https://github.com/vercel/next.js/discussions/31188 - https://github.com/vercel/next.js/discussions/39300 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [x] 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` - [x] Integration tests added - [x] 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)
2022-10-20 03:36:05 +02:00
/* eslint-env jest */
import { NextInstance } from 'test/lib/next-modes/base'
import { fetchViaHTTP } from 'next-test-utils'
import { createNext, FileRef } from 'e2e-utils'
import cheerio from 'cheerio'
import path from 'path'
describe('app-dir with middleware', () => {
if ((global as any).isNextDeploy) {
it('should skip next deploy for now', () => {})
return
}
let next: NextInstance
afterAll(() => next.destroy())
beforeAll(async () => {
next = await createNext({
files: new FileRef(path.join(__dirname, 'app-middleware')),
dependencies: {
react: 'experimental',
'react-dom': 'experimental',
Support overriding request headers in middlewares (#41380) This PR adds a feature in middleware to add, modify, or delete request headers. This feature is quite useful to pass data from middleware to Serverless/Edge API routes. ### Questions for Reviewers - Should we deny modifying standard request headers like `Transfer-Encoding`? - Should we throw an error if the header is too large? Real-world HTTP servers will accept up to only 8KB - 32KB. ### New APIs Adds a new option `request.headers` to the `MiddlewareResponseInit` parameter in `NextResponse.next()` and `NextResponse.rewrite()`. It's a [`Header`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object holding *all* request headers. Specifically: ```ts interface MiddlewareResponseInit extends ResponseInit { request?: { headers?: Headers } } ``` ### Example ```ts // pages/api/hello.ts export default (req, res) => { const valueFromMiddleware = req.headers['x-hello-from-middleware'] return res.send(valueFromMiddleware) } // middleware.ts import { NextRequest, NextResponse } from 'next/server' export default function middleware(request: NextRequest) { // Clone request headers const headers = new Headers(request.headers); // Add a new request header headers.set('x-hello-from-middleware', 'foo'); // Delete a request header from the client headers.delete('x-from-client'); const resp = NextResponse.next({ // New option `request.headers` which accepts a Headers object // overrides request headers with the specified new ones. request: { headers } }); // You can still set *response* headers to the client, as before. resp.headers.set('x-hello-client', 'bar'); return resp; } ``` ### New middleware headers - `x-middleware-override-headers`: A comma separated list of *all* request header names. Headers not listed will be deleted. - `x-middleware-request-<name>`: A new value for the header `<name>`. ## Related Discussions - https://github.com/vercel/next.js/discussions/31188 - https://github.com/vercel/next.js/discussions/39300 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [x] 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` - [x] Integration tests added - [x] 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)
2022-10-20 03:36:05 +02:00
},
})
})
describe.each([
{
title: 'Serverless Functions',
path: '/api/dump-headers-serverless',
toJson: (res: Response) => res.json(),
},
{
title: 'Edge Functions',
path: '/api/dump-headers-edge',
toJson: (res: Response) => res.json(),
},
{
title: 'next/headers',
path: '/headers',
toJson: async (res: Response) => {
const $ = cheerio.load(await res.text())
return JSON.parse($('#headers').text())
},
},
])('Mutate request headers for $title', ({ path, toJson }) => {
it(`Adds new headers`, async () => {
const res = await fetchViaHTTP(next.url, path, null, {
headers: {
'x-from-client': 'hello-from-client',
},
})
expect(await toJson(res)).toMatchObject({
'x-from-client': 'hello-from-client',
'x-from-middleware': 'hello-from-middleware',
})
})
it(`Deletes headers`, async () => {
const res = await fetchViaHTTP(
next.url,
path,
{
'remove-headers': 'x-from-client1,x-from-client2',
},
{
headers: {
'x-from-client1': 'hello-from-client',
'X-From-Client2': 'hello-from-client',
},
}
)
const json = await toJson(res)
expect(json).not.toHaveProperty('x-from-client1')
expect(json).not.toHaveProperty('X-From-Client2')
expect(json).toMatchObject({
'x-from-middleware': 'hello-from-middleware',
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client1')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client2')).toBeNull()
})
it(`Updates headers`, async () => {
const res = await fetchViaHTTP(
next.url,
path,
{
'update-headers':
'x-from-client1=new-value1,x-from-client2=new-value2',
},
{
headers: {
'x-from-client1': 'old-value1',
'X-From-Client2': 'old-value2',
'x-from-client3': 'old-value3',
},
}
)
expect(await toJson(res)).toMatchObject({
'x-from-client1': 'new-value1',
'x-from-client2': 'new-value2',
'x-from-client3': 'old-value3',
'x-from-middleware': 'hello-from-middleware',
})
// Should not be included in response headers.
expect(res.headers.get('x-middleware-override-headers')).toBeNull()
expect(
res.headers.get('x-middleware-request-x-from-middleware')
).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client1')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client2')).toBeNull()
expect(res.headers.get('x-middleware-request-x-from-client3')).toBeNull()
})
})
})