-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#7226 - fixes NodeJS adapter for multiple set-cookie headers (and oth…
…er header issues) (#7227) * Utilizes the new standard WebAPI Fetch Headers.getSetCookie() function to safely handle multiple set-cookie headers when converting from a WebAPI Response to a NodeJS ServerResponse Modifies the existing nodeMiddleware logic which first set AstroCookies on ServerResponse.setHeader(...) and then called ServerResponse.writeHead(status, Response.headers) which means any that if the WebAPI Response had any set-cookie headers on it, they would replace anything from AstroCookies. The new logic delegates appending AstroCookie values onto the WebAPI Response Headers object, so that a single unified function safely converts the WebAPI Response Headers into a NodeJS compatible OutgoingHttpHeaders object utilizing the new standard Headers.getSetCookie() function provided by the undici WebAPI polyfills. Plus extensive test coverage. * #7226 - changeset for NodeJS adapter set-cookie fix * fixing all double quotes to single quotes --------- Co-authored-by: Alex Sherwin <[email protected]> Co-authored-by: Nate Moore <[email protected]>
- Loading branch information
1 parent
409c600
commit 4929332
Showing
24 changed files
with
404 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@astrojs/node': minor | ||
--- | ||
|
||
Fixes NodeJS adapter for multiple set-cookie headers and combining AstroCookies and Response.headers cookies |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
packages/integrations/node/src/createOutgoingHttpHeaders.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { OutgoingHttpHeaders } from 'http'; | ||
|
||
/** | ||
* Takes in a nullable WebAPI Headers object and produces a NodeJS OutgoingHttpHeaders object suitable for usage | ||
* with ServerResponse.writeHead(..) or ServerResponse.setHeader(..) | ||
* | ||
* @param webHeaders WebAPI Headers object | ||
* @returns NodeJS OutgoingHttpHeaders object with multiple set-cookie handled as an array of values | ||
*/ | ||
export const createOutgoingHttpHeaders = (webHeaders: Headers | undefined | null): OutgoingHttpHeaders | undefined => { | ||
if (!webHeaders) { | ||
return undefined; | ||
} | ||
|
||
// re-type to access Header.getSetCookie() | ||
const headers = webHeaders as HeadersWithGetSetCookie; | ||
|
||
// at this point, a multi-value'd set-cookie header is invalid (it was concatenated as a single CSV, which is not valid for set-cookie) | ||
const nodeHeaders: OutgoingHttpHeaders = Object.fromEntries(headers.entries()); | ||
|
||
if (Object.keys(nodeHeaders).length === 0) { | ||
return undefined; | ||
} | ||
|
||
// if there is > 1 set-cookie header, we have to fix it to be an array of values | ||
if (headers.has('set-cookie')) { | ||
const cookieHeaders = headers.getSetCookie(); | ||
if (cookieHeaders.length > 1) { | ||
// the Headers.entries() API already normalized all header names to lower case so we can safely index this as 'set-cookie' | ||
nodeHeaders['set-cookie'] = cookieHeaders; | ||
} | ||
} | ||
|
||
return nodeHeaders; | ||
}; | ||
|
||
interface HeadersWithGetSetCookie extends Headers { | ||
// the @astrojs/webapi polyfill makes this available (as of [email protected]), but tsc doesn't pick it up on the built-in Headers type from DOM lib | ||
getSetCookie(): string[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
packages/integrations/node/test/createOutgoingHttpHeaders.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { expect } from 'chai'; | ||
|
||
import { createOutgoingHttpHeaders } from '../dist/createOutgoingHttpHeaders.js'; | ||
|
||
describe('createOutgoingHttpHeaders', () => { | ||
|
||
it('undefined input headers', async () => { | ||
const result = createOutgoingHttpHeaders(undefined); | ||
expect(result).to.equal(undefined); | ||
}); | ||
|
||
it('null input headers', async () => { | ||
const result = createOutgoingHttpHeaders(undefined); | ||
expect(result).to.equal(undefined); | ||
}); | ||
|
||
it('Empty Headers', async () => { | ||
const headers = new Headers(); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.equal(undefined); | ||
}); | ||
|
||
it('Headers with single key', async () => { | ||
const headers = new Headers(); | ||
headers.append('x-test', 'hello world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ 'x-test': 'hello world' }); | ||
}); | ||
|
||
it('Headers with multiple keys', async () => { | ||
const headers = new Headers(); | ||
headers.append('x-test1', 'hello'); | ||
headers.append('x-test2', 'world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ 'x-test1': 'hello', 'x-test2': 'world' }); | ||
}); | ||
|
||
it('Headers with multiple values (not set-cookie)', async () => { | ||
const headers = new Headers(); | ||
headers.append('x-test', 'hello'); | ||
headers.append('x-test', 'world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ 'x-test': 'hello, world' }); | ||
}); | ||
|
||
it('Headers with multiple values (set-cookie special case)', async () => { | ||
const headers = new Headers(); | ||
headers.append('set-cookie', 'hello'); | ||
headers.append('set-cookie', 'world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ 'set-cookie': ['hello', 'world'] }); | ||
}); | ||
|
||
it('Headers with multiple values (set-cookie case handling)', async () => { | ||
const headers = new Headers(); | ||
headers.append('Set-cookie', 'hello'); | ||
headers.append('Set-Cookie', 'world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ 'set-cookie': ['hello', 'world'] }); | ||
}); | ||
|
||
it('Headers with all use cases', async () => { | ||
const headers = new Headers(); | ||
headers.append('x-single', 'single'); | ||
headers.append('x-triple', 'one'); | ||
headers.append('x-triple', 'two'); | ||
headers.append('x-triple', 'three'); | ||
headers.append('Set-cookie', 'hello'); | ||
headers.append('Set-Cookie', 'world'); | ||
const result = createOutgoingHttpHeaders(headers); | ||
expect(result).to.deep.equal({ | ||
'x-single': 'single', | ||
'x-triple': 'one, two, three', | ||
'set-cookie': ['hello', 'world'], | ||
}); | ||
}); | ||
|
||
}); |
9 changes: 9 additions & 0 deletions
9
packages/integrations/node/test/fixtures/headers/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "@test/nodejs-headers", | ||
"version": "0.0.0", | ||
"private": true, | ||
"dependencies": { | ||
"astro": "workspace:*", | ||
"@astrojs/node": "workspace:*" | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...tegrations/node/test/fixtures/headers/src/pages/astro/component-astro-cookies-multi.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
Astro.cookies.set('from1', 'astro1'); | ||
Astro.cookies.set('from2', 'astro2'); | ||
--- | ||
<p>hello world</p> |
4 changes: 4 additions & 0 deletions
4
...egrations/node/test/fixtures/headers/src/pages/astro/component-astro-cookies-single.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
Astro.cookies.set('from1', 'astro1'); | ||
--- | ||
<p>hello world</p> |
7 changes: 7 additions & 0 deletions
7
...ns/node/test/fixtures/headers/src/pages/astro/component-astro-response-cookie-multi.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
Astro.response.headers.append('set-cookie', 'from1=response1'); | ||
Astro.response.headers.append('set-cookie', 'from2=response2'); | ||
Astro.cookies.set('from3', 'astro1'); | ||
Astro.cookies.set('from4', 'astro2'); | ||
--- | ||
<p>hello world</p> |
5 changes: 5 additions & 0 deletions
5
...s/node/test/fixtures/headers/src/pages/astro/component-astro-response-cookie-single.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
Astro.response.headers.append('set-cookie', 'from1=response1'); | ||
Astro.cookies.set('from1', 'astro1'); | ||
--- | ||
<p>hello world</p> |
5 changes: 5 additions & 0 deletions
5
...rations/node/test/fixtures/headers/src/pages/astro/component-response-cookies-multi.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
Astro.response.headers.append('set-cookie', 'from1=value1'); | ||
Astro.response.headers.append('set-cookie', 'from2=value2'); | ||
--- | ||
<p>hello world</p> |
4 changes: 4 additions & 0 deletions
4
...ations/node/test/fixtures/headers/src/pages/astro/component-response-cookies-single.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
Astro.response.headers.append('set-cookie', 'from1=value1'); | ||
--- | ||
<p>hello world</p> |
9 changes: 9 additions & 0 deletions
9
packages/integrations/node/test/fixtures/headers/src/pages/endpoints/astro-cookies-multi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { APIContext } from 'astro'; | ||
|
||
export async function get({ request, cookies }: APIContext) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
cookies.set('from1', 'astro1'); | ||
cookies.set('from2', 'astro2'); | ||
return new Response('hello world', { headers }); | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/integrations/node/test/fixtures/headers/src/pages/endpoints/astro-cookies-single.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { APIContext } from 'astro'; | ||
|
||
export async function get({ request, cookies }: APIContext) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
cookies.set('from1', 'astro1'); | ||
return new Response('hello world', { headers }); | ||
} |
11 changes: 11 additions & 0 deletions
11
...ntegrations/node/test/fixtures/headers/src/pages/endpoints/astro-response-cookie-multi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { APIContext } from 'astro'; | ||
|
||
export async function get({ request, cookies }: APIContext) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('set-cookie', 'from1=response1'); | ||
headers.append('set-cookie', 'from2=response2'); | ||
cookies.set('from3', 'astro1'); | ||
cookies.set('from4', 'astro2'); | ||
return new Response('hello world', { headers }); | ||
} |
9 changes: 9 additions & 0 deletions
9
...tegrations/node/test/fixtures/headers/src/pages/endpoints/astro-response-cookie-single.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { APIContext } from 'astro'; | ||
|
||
export async function get({ request, cookies }: APIContext) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('set-cookie', 'from1=response1'); | ||
cookies.set('from1', 'astro1'); | ||
return new Response('hello world', { headers }); | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/integrations/node/test/fixtures/headers/src/pages/endpoints/kitchen-sink.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export async function get({ request }: { request: Request }) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('x-SINGLE', 'single'); | ||
headers.append('X-triple', 'one'); | ||
headers.append('x-Triple', 'two'); | ||
headers.append('x-TRIPLE', 'three'); | ||
headers.append('SET-cookie', 'hello1=world1'); | ||
headers.append('Set-Cookie', 'hello2=world2'); | ||
return new Response('hello world', { headers }); | ||
} |
7 changes: 7 additions & 0 deletions
7
...ges/integrations/node/test/fixtures/headers/src/pages/endpoints/response-cookies-multi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export async function get({ request }: { request: Request }) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('Set-Cookie', 'hello1=world1'); | ||
headers.append('SET-COOKIE', 'hello2=world2'); | ||
return new Response('hello world', { headers }); | ||
} |
6 changes: 6 additions & 0 deletions
6
...es/integrations/node/test/fixtures/headers/src/pages/endpoints/response-cookies-single.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export async function get({ request }: { request: Request }) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('Set-Cookie', 'hello1=world1'); | ||
return new Response('hello world', { headers }); | ||
} |
4 changes: 4 additions & 0 deletions
4
...egrations/node/test/fixtures/headers/src/pages/endpoints/response-empty-headers-object.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export async function get({ request }: { request: Request }) { | ||
const headers = new Headers(); | ||
return new Response('hello world', { headers }); | ||
} |
3 changes: 3 additions & 0 deletions
3
...tions/node/test/fixtures/headers/src/pages/endpoints/response-undefined-headers-object.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export async function get({ request }: { request: Request }) { | ||
return new Response('hello world', { headers: undefined }); | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/integrations/node/test/fixtures/headers/src/pages/endpoints/simple.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export async function get({ request }: { request: Request }) { | ||
const headers = new Headers(); | ||
headers.append('content-type', 'text/plain;charset=utf-8'); | ||
headers.append('X-HELLO', 'world'); | ||
return new Response('hello world', { headers }); | ||
} |
Oops, something went wrong.