Allow setting multiple cookies in Netlify adapter (#3092)

* Allow setting multiple cookies in Netlify adapter

* Adds a changeset

* Set the response status code

* Add a comment on why this is needed
This commit is contained in:
Matthew Phillips 2022-04-12 16:50:10 -04:00 committed by GitHub
parent c459c87325
commit a5caf08e24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/netlify': patch
---
Fixes setting multiple cookies with the Netlify adapter

View file

@ -33,14 +33,37 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
};
}
const response = await app.render(request);
const response: Response = await app.render(request);
const responseBody = await response.text();
return {
statusCode: 200,
headers: Object.fromEntries(response.headers.entries()),
const responseHeaders = Object.fromEntries(response.headers.entries());
const fnResponse: any = {
statusCode: response.status,
headers: responseHeaders,
body: responseBody,
};
// Special-case set-cookie which has to be set an different way :/
// The fetch API does not have a way to get multiples of a single header, but instead concatenates
// them. There are non-standard ways to do it, and node-fetch gives us headers.raw()
// See https://github.com/whatwg/fetch/issues/973 for discussion
if (response.headers.has('set-cookie') && 'raw' in response.headers) {
// Node fetch allows you to get the raw headers, which includes multiples of the same type.
// This is needed because Set-Cookie *must* be called for each cookie, and can't be
// concatenated together.
type HeadersWithRaw = Headers & {
raw: () => Record<string, string[]>;
};
const rawPacked = (response.headers as HeadersWithRaw).raw();
if('set-cookie' in rawPacked) {
fnResponse.multiValueHeaders = {
'set-cookie': rawPacked['set-cookie']
}
}
}
return fnResponse;
};
return { handler };

View file

@ -0,0 +1,50 @@
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from '../../../astro/test/test-utils.js';
import netlifyAdapter from '../dist/index.js';
import { fileURLToPath } from 'url';
describe('Cookies', () => {
/** @type {import('../../../astro/test/test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: new URL('./fixtures/cookies/', import.meta.url).toString(),
experimental: {
ssr: true,
},
adapter: netlifyAdapter({
dist: new URL('./fixtures/cookies/dist/', import.meta.url),
}),
site: `http://example.com`,
vite: {
resolve: {
alias: {
'@astrojs/netlify/netlify-functions.js': fileURLToPath(
new URL('../dist/netlify-functions.js', import.meta.url)
),
},
},
},
});
await fixture.build();
});
it('Can set multiple', async () => {
const entryURL = new URL('./fixtures/cookies/dist/functions/entry.mjs', import.meta.url);
const { handler } = await import(entryURL);
const resp = await handler({
httpMethod: 'POST',
headers: {},
rawUrl: 'http://example.com/login',
body: '{}',
isBase64Encoded: false
});
expect(resp.statusCode).to.equal(301);
expect(resp.headers.location).to.equal('/');
expect(resp.multiValueHeaders).to.be.deep.equal({
'set-cookie': [ 'foo=foo; HttpOnly', 'bar=bar; HttpOnly' ]
});
});
});

View file

@ -0,0 +1,6 @@
<html>
<head><title>Testing</title></head>
<body>
<h1>Testing</h1>
</body>
</html>

View file

@ -0,0 +1,12 @@
export function post() {
const headers = new Headers();
headers.append('Set-Cookie', `foo=foo; HttpOnly`);
headers.append('Set-Cookie', `bar=bar; HttpOnly`);
headers.append('Location', '/');
return new Response('', {
status: 301,
headers,
});
}