Skip to content

Commit 3828ebe

Browse files
authored
Fix middleware header propagation (vercel#30288)
1 parent 2f329d2 commit 3828ebe

File tree

6 files changed

+68
-13
lines changed

6 files changed

+68
-13
lines changed

packages/next/server/next-server.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ export default class Server {
624624

625625
const subreq = params.request.headers[`x-middleware-subrequest`]
626626
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
627-
627+
const allHeaders = new Headers()
628628
let result: FetchEventResult | null = null
629629

630630
for (const middleware of this.middleware || []) {
@@ -667,6 +667,10 @@ export default class Server {
667667
},
668668
})
669669

670+
for (let [key, value] of result.response.headers) {
671+
allHeaders.append(key, value)
672+
}
673+
670674
if (!this.renderOpts.dev) {
671675
result.waitUntil.catch((error) => {
672676
console.error(`Uncaught: middleware waitUntil errored`, error)
@@ -681,6 +685,10 @@ export default class Server {
681685

682686
if (!result) {
683687
this.render404(params.request, params.response, params.parsed)
688+
} else {
689+
for (let [key, value] of allHeaders) {
690+
result.response.headers.set(key, value)
691+
}
684692
}
685693

686694
return result
@@ -1156,7 +1164,11 @@ export default class Server {
11561164

11571165
for (const [key, value] of result.response.headers.entries()) {
11581166
if (key !== 'content-encoding') {
1159-
res.setHeader(key, value)
1167+
if (key.toLowerCase() === 'set-cookie') {
1168+
res.setHeader(key, value.split(', '))
1169+
} else {
1170+
res.setHeader(key, value)
1171+
}
11601172
}
11611173
}
11621174

packages/next/server/web/utils.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,28 @@ export function notImplemented(name: string, method: string): any {
2020
)
2121
}
2222

23-
export function fromNodeHeaders(object: NodeHeaders) {
24-
const headers: { [k: string]: string } = {}
25-
for (let headerKey in object) {
26-
const headerValue = object[headerKey]
27-
if (Array.isArray(headerValue)) {
28-
headers[headerKey] = headerValue.join('; ')
29-
} else if (headerValue) {
30-
headers[headerKey] = String(headerValue)
23+
export function fromNodeHeaders(object: NodeHeaders): Headers {
24+
const headers = new Headers()
25+
for (let [key, value] of Object.entries(object)) {
26+
const values = Array.isArray(value) ? value : [value]
27+
for (let v of values) {
28+
if (v !== undefined) {
29+
headers.append(key, v)
30+
}
3131
}
3232
}
3333
return headers
3434
}
3535

3636
export function toNodeHeaders(headers?: Headers): NodeHeaders {
37-
const object: NodeHeaders = {}
37+
const result: NodeHeaders = {}
3838
if (headers) {
3939
for (const [key, value] of headers.entries()) {
40-
object[key] = value.includes(';') ? value.split(';') : value
40+
result[key] = value
41+
if (key.toLowerCase() === 'set-cookie') {
42+
result[key] = value.split(', ')
43+
}
4144
}
4245
}
43-
return object
46+
return result
4447
}

test/integration/middleware-core/pages/responses/_middleware.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ export async function middleware(request, ev) {
1515
next.headers.set('x-nested-header', 'valid')
1616
}
1717

18+
// Ensure deep can append to this value
19+
if (url.searchParams.get('append-me') === 'true') {
20+
next.headers.append('x-append-me', 'top')
21+
}
22+
23+
// Ensure deep can append to this value
24+
if (url.searchParams.get('cookie-me') === 'true') {
25+
next.headers.append('set-cookie', 'chocochip')
26+
}
27+
1828
// Sends a header
1929
if (url.pathname === '/responses/header') {
2030
next.headers.set('x-first-header', 'valid')
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export async function middleware(request, _event) {
4+
const next = NextResponse.next()
5+
next.headers.set('x-deep-header', 'valid')
6+
next.headers.append('x-append-me', 'deep')
7+
next.headers.append('set-cookie', 'oatmeal')
8+
return next
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
function Deep() {
2+
return (
3+
<div>
4+
<h1>Deep</h1>
5+
<p>This is a deep page with deep middleware, check the headers</p>
6+
</div>
7+
)
8+
}
9+
10+
export default Deep

test/integration/middleware-core/test/index.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,17 @@ function responseTests(locale = '') {
325325
)
326326
expect(res.headers.get('x-first-header')).toBe('valid')
327327
})
328+
329+
it(`${locale} should respond with top level headers and append deep headers`, async () => {
330+
const res = await fetchViaHTTP(
331+
context.appPort,
332+
`${locale}/responses/deep?nested-header=true&append-me=true&cookie-me=true`
333+
)
334+
expect(res.headers.get('x-nested-header')).toBe('valid')
335+
expect(res.headers.get('x-deep-header')).toBe('valid')
336+
expect(res.headers.get('x-append-me')).toBe('top, deep')
337+
expect(res.headers.raw()['set-cookie']).toEqual(['chocochip', 'oatmeal'])
338+
})
328339
}
329340

330341
function interfaceTests(locale = '') {

0 commit comments

Comments
 (0)