summaryrefslogtreecommitdiff
path: root/packages/integrations/node/src
diff options
context:
space:
mode:
authorGravatar Alex Sherwin <alex-sherwin@users.noreply.github.com> 2023-06-06 11:09:16 -0400
committerGravatar GitHub <noreply@github.com> 2023-06-06 10:09:16 -0500
commit4929332c3210d1634b8607c7736d9049860a2079 (patch)
treef232675fb2d6820e6262a0f50aaa49efa42e9e0e /packages/integrations/node/src
parent409c60028aaab09b8f2383ef5730531cd23db4ba (diff)
downloadastro-4929332c3210d1634b8607c7736d9049860a2079.tar.gz
astro-4929332c3210d1634b8607c7736d9049860a2079.tar.zst
astro-4929332c3210d1634b8607c7736d9049860a2079.zip
#7226 - fixes NodeJS adapter for multiple set-cookie headers (and other 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 <alex.sherwin@acadia.inc> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
Diffstat (limited to 'packages/integrations/node/src')
-rw-r--r--packages/integrations/node/src/createOutgoingHttpHeaders.ts40
-rw-r--r--packages/integrations/node/src/nodeMiddleware.ts9
2 files changed, 47 insertions, 2 deletions
diff --git a/packages/integrations/node/src/createOutgoingHttpHeaders.ts b/packages/integrations/node/src/createOutgoingHttpHeaders.ts
new file mode 100644
index 000000000..5b99cfa4f
--- /dev/null
+++ b/packages/integrations/node/src/createOutgoingHttpHeaders.ts
@@ -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 undici@5.19.0), but tsc doesn't pick it up on the built-in Headers type from DOM lib
+ getSetCookie(): string[];
+}
diff --git a/packages/integrations/node/src/nodeMiddleware.ts b/packages/integrations/node/src/nodeMiddleware.ts
index c23cdb89c..492130daa 100644
--- a/packages/integrations/node/src/nodeMiddleware.ts
+++ b/packages/integrations/node/src/nodeMiddleware.ts
@@ -3,6 +3,7 @@ import type { IncomingMessage, ServerResponse } from 'http';
import type { Readable } from 'stream';
import { responseIterator } from './response-iterator';
import type { Options } from './types';
+import { createOutgoingHttpHeaders } from './createOutgoingHttpHeaders';
export default function (app: NodeApp, mode: Options['mode']) {
return async function (
@@ -44,12 +45,16 @@ async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse:
if (app.setCookieHeaders) {
const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(webResponse));
+
if (setCookieHeaders.length) {
- res.setHeader('Set-Cookie', setCookieHeaders);
+ for (const setCookieHeader of setCookieHeaders) {
+ webResponse.headers.append('set-cookie', setCookieHeader);
+ }
}
}
- res.writeHead(status, Object.fromEntries(headers.entries()));
+ const nodeHeaders = createOutgoingHttpHeaders(headers);
+ res.writeHead(status, nodeHeaders);
if (webResponse.body) {
try {
for await (const chunk of responseIterator(webResponse) as unknown as Readable) {