Skip to content

Commit

Permalink
fix(vercel): support node-fetch and Node 18 fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
JuanM04 committed Dec 12, 2022
1 parent a219c41 commit 76d076c
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/modern-camels-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/vercel': patch
---

Support node-fetch and Node 18 fetch
11 changes: 10 additions & 1 deletion packages/integrations/vercel/src/serverless/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ import type { SSRManifest } from 'astro';
import { App } from 'astro/app';
import type { IncomingMessage, ServerResponse } from 'node:http';

import { getRequest, setResponse } from './request-transform.js';
import * as requestTransformLegacy from './request-transform/legacy.js';
import * as requestTransformNode18 from './request-transform/node18.js';

polyfill(globalThis, {
exclude: 'window document',
});

// Node 18+ has a new API for request/response, while older versions use node-fetch
// When we drop support for Node 14, we can remove the legacy code by switching to undici

const nodeVersion = parseInt(process.version.split('.')[0].slice(1)); // 'v14.17.0' -> 14

const { getRequest, setResponse } =
nodeVersion >= 18 ? requestTransformNode18 : requestTransformLegacy;

export const createExports = (manifest: SSRManifest) => {
const app = new App(manifest);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { App } from 'astro/app';
import type { IncomingMessage, ServerResponse } from 'node:http';
import { Readable } from 'node:stream';

const clientAddressSymbol = Symbol.for('astro.clientAddress');

/*
Credits to the SvelteKit team
https://github.com/sveltejs/kit/blob/69913e9fda054fa6a62a80e2bb4ee7dca1005796/packages/kit/src/node.js
*/

function get_raw_body(req: IncomingMessage) {
return new Promise<Uint8Array | null>((fulfil, reject) => {
const h = req.headers;

if (!h['content-type']) {
return fulfil(null);
}

req.on('error', reject);

const length = Number(h['content-length']);

// https://github.com/jshttp/type-is/blob/c1f4388c71c8a01f79934e68f630ca4a15fffcd6/index.js#L81-L95
if (isNaN(length) && h['transfer-encoding'] == null) {
return fulfil(null);
}

let data = new Uint8Array(length || 0);

if (length > 0) {
let offset = 0;
req.on('data', (chunk) => {
const new_len = offset + Buffer.byteLength(chunk);

if (new_len > length) {
return reject({
status: 413,
reason: 'Exceeded "Content-Length" limit',
});
}

data.set(chunk, offset);
offset = new_len;
});
} else {
req.on('data', (chunk) => {
const new_data = new Uint8Array(data.length + chunk.length);
new_data.set(data, 0);
new_data.set(chunk, data.length);
data = new_data;
});
}

req.on('end', () => {
fulfil(data);
});
});
}

export async function getRequest(base: string, req: IncomingMessage): Promise<Request> {
let headers = req.headers as Record<string, string>;
if (req.httpVersionMajor === 2) {
// we need to strip out the HTTP/2 pseudo-headers because node-fetch's
// Request implementation doesn't like them
headers = Object.assign({}, headers);
delete headers[':method'];
delete headers[':path'];
delete headers[':authority'];
delete headers[':scheme'];
}
const request = new Request(base + req.url, {
method: req.method,
headers,
body: await get_raw_body(req), // TODO stream rather than buffer
});
Reflect.set(request, clientAddressSymbol, headers['x-forwarded-for']);
return request;
}

export async function setResponse(
app: App,
res: ServerResponse,
response: Response
): Promise<void> {
const headers = Object.fromEntries(response.headers);

if (response.headers.has('set-cookie')) {
// @ts-expect-error (headers.raw() is non-standard)
headers['set-cookie'] = response.headers.raw()['set-cookie'];
}

if (app.setCookieHeaders) {
const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(response));
if (setCookieHeaders.length) {
res.setHeader('Set-Cookie', setCookieHeaders);
}
}

res.writeHead(response.status, headers);

if (response.body instanceof Readable) {
response.body.pipe(res);
} else {
if (response.body) {
res.write(await response.arrayBuffer());
}

res.end();
}
}

0 comments on commit 76d076c

Please sign in to comment.