Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quality of life updates for the node adapter #9582

Merged
merged 12 commits into from
Jan 12, 2024
5 changes: 5 additions & 0 deletions .changeset/weak-apes-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/node": minor
lilnasy marked this conversation as resolved.
Show resolved Hide resolved
---

Reorganizes internals to be more maintainable.
11 changes: 8 additions & 3 deletions packages/astro/src/core/app/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,20 @@ export class NodeApp extends App {
try {
const reader = body.getReader();
destination.on('close', () => {
reader.cancel();
// Cancelling the reader may reject not just because of
// an error in the ReadableStream's cancel callback, but
// also because of an error anywhere in the stream.
reader.cancel().catch(err => {
console.error(`There was an uncaught error in the middle of the stream while rendering ${destination.req.url}.`, err);
});
});
let result = await reader.read();
while (!result.done) {
destination.write(result.value);
result = await reader.read();
}
} catch (err: any) {
console.error(err?.stack || err?.message || String(err));
// the error will be logged by the "on end" callback above
} catch {
destination.write('Internal server error');
}
}
Expand Down
34 changes: 0 additions & 34 deletions packages/integrations/node/src/createOutgoingHttpHeaders.ts

This file was deleted.

48 changes: 0 additions & 48 deletions packages/integrations/node/src/get-network-address.ts

This file was deleted.

131 changes: 0 additions & 131 deletions packages/integrations/node/src/http-server.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/integrations/node/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AstroAdapter, AstroIntegration } from 'astro';
import { AstroError } from 'astro/errors';
import type { AstroAdapter, AstroIntegration } from 'astro';
import type { Options, UserOptions } from './types.js';

export function getAdapter(options: Options): AstroAdapter {
return {
name: '@astrojs/node',
Expand Down
84 changes: 84 additions & 0 deletions packages/integrations/node/src/log-listening-on.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os from "node:os";
import type http from "node:http";
import https from "node:https";
import type { AstroIntegrationLogger } from "astro";
import type { Options } from './types.js';
import type { AddressInfo } from "node:net";

export default async function logListeningOn(logger: AstroIntegrationLogger, server: http.Server | https.Server, options: Pick<Options, "host">) {
await new Promise<void>(resolve => server.once('listening', resolve))
const protocol = server instanceof https.Server ? 'https' : 'http';
// Allow to provide host value at runtime
const host = getResolvedHostForHttpServer(
process.env.HOST !== undefined && process.env.HOST !== '' ? process.env.HOST : options.host
);
const { port } = server.address() as AddressInfo;
const address = getNetworkAddress(protocol, host, port);

if (host === undefined) {
logger.info(
`Server listening on \n local: ${address.local[0]} \t\n network: ${address.network[0]}\n`
);
} else {
logger.info(`Server listening on ${address.local[0]}`);
}
}

function getResolvedHostForHttpServer(host: string | boolean) {
if (host === false) {
// Use a secure default
return '127.0.0.1';
lilnasy marked this conversation as resolved.
Show resolved Hide resolved
} else if (host === true) {
// If passed --host in the CLI without arguments
return undefined; // undefined typically means 0.0.0.0 or :: (listen on all IPs)
} else {
return host;
}
}

interface NetworkAddressOpt {
local: string[];
network: string[];
}

const wildcardHosts = new Set(['0.0.0.0', '::', '0000:0000:0000:0000:0000:0000:0000:0000']);

// this code from vite https://github.com/vitejs/vite/blob/d09bbd093a4b893e78f0bbff5b17c7cf7821f403/packages/vite/src/node/utils.ts#L892-L914
export function getNetworkAddress(
protocol: 'http' | 'https' = 'http',
hostname: string | undefined,
port: number,
base?: string
) {
const NetworkAddress: NetworkAddressOpt = {
local: [],
network: [],
};
Object.values(os.networkInterfaces())
.flatMap((nInterface) => nInterface ?? [])
.filter(
(detail) =>
detail &&
detail.address &&
(detail.family === 'IPv4' ||
// @ts-expect-error Node 18.0 - 18.3 returns number
detail.family === 4)
)
.forEach((detail) => {
let host = detail.address.replace(
'127.0.0.1',
hostname === undefined || wildcardHosts.has(hostname) ? 'localhost' : hostname
);
// ipv6 host
if (host.includes(':')) {
host = `[${host}]`;
}
const url = `${protocol}://${host}:${port}${base ? base : ''}`;
if (detail.address.includes('127.0.0.1')) {
NetworkAddress.local.push(url);
} else {
NetworkAddress.network.push(url);
}
});
return NetworkAddress;
}
43 changes: 43 additions & 0 deletions packages/integrations/node/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import createAppListener from './serve-app.js';
import type { RequestHandler } from "./types.js";
import type { NodeApp } from "astro/app/node";

/**
* Creates a middleware that can be used with Express, Connect, etc.
*
* Similar to `createListener` but can additionally be placed in the express
* chain as an error middleware.
*
* https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
*/
export default function createMiddleware(
app: NodeApp,
): RequestHandler {
const listener = createAppListener(app)
const logger = app.getAdapterLogger()
// using spread args because express trips up if the function's
// stringified body includes req, res, next, locals directly
return async function (...args) {
// assume normal invocation at first
const [req, res, next, locals] = args;
// short circuit if it is an error invocation
if (req instanceof Error) {
const error = req;
if (next) {
return next(error);
} else {
throw error;
}
}
try {
await listener(req, res, next, locals);
} catch (err) {
logger.error(`Could not render ${req.url}`);
console.error(err);
if (!res.headersSent) {
res.writeHead(500, `Server error`);
res.end();
}
}
}
}
Loading