mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-21 05:16:20 -07:00
fix(stats): harden server lifecycle and verify compiled runtime (#261)
This commit is contained in:
@@ -50,8 +50,26 @@ async function writeFetchResponse(res: ServerResponse, response: Response): Prom
|
||||
res.end(Buffer.from(await response.arrayBuffer()));
|
||||
}
|
||||
|
||||
function startNodeHttpServer(app: Hono, config: StatsServerConfig): { close: () => void } {
|
||||
const server = http.createServer((req, res) => {
|
||||
export interface StatsServer {
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
const SHUTDOWN_GRACE_MS = 1_000;
|
||||
|
||||
type BunServe = (options: {
|
||||
fetch: (typeof Hono.prototype)['fetch'];
|
||||
port: number;
|
||||
hostname: string;
|
||||
}) => {
|
||||
stop: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
export function startNodeHttpServer(
|
||||
app: Hono,
|
||||
config: StatsServerConfig,
|
||||
createServer: (listener: http.RequestListener) => http.Server = http.createServer,
|
||||
): Promise<StatsServer> {
|
||||
const server = createServer((req, res) => {
|
||||
void (async () => {
|
||||
try {
|
||||
await writeFetchResponse(res, await app.fetch(toFetchRequest(req)));
|
||||
@@ -61,12 +79,33 @@ function startNodeHttpServer(app: Hono, config: StatsServerConfig): { close: ()
|
||||
}
|
||||
})();
|
||||
});
|
||||
server.listen(config.port, '127.0.0.1');
|
||||
return {
|
||||
close: () => {
|
||||
server.close();
|
||||
},
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const handleStartupError = (error: Error): void => {
|
||||
server.removeListener('listening', handleListening);
|
||||
reject(error);
|
||||
};
|
||||
const handleListening = (): void => {
|
||||
server.removeListener('error', handleStartupError);
|
||||
let closePromise: Promise<void> | null = null;
|
||||
resolve({
|
||||
close: () => {
|
||||
closePromise ??= new Promise<void>((closeResolve, closeReject) => {
|
||||
const forceClose = setTimeout(() => server.closeAllConnections(), SHUTDOWN_GRACE_MS);
|
||||
server.close((error) => {
|
||||
clearTimeout(forceClose);
|
||||
if (error) closeReject(error);
|
||||
else closeResolve();
|
||||
});
|
||||
});
|
||||
return closePromise;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
server.once('error', handleStartupError);
|
||||
server.once('listening', handleListening);
|
||||
server.listen(config.port, '127.0.0.1');
|
||||
});
|
||||
}
|
||||
|
||||
export interface StatsServerConfig {
|
||||
@@ -125,7 +164,10 @@ export function createStatsApp(
|
||||
return app;
|
||||
}
|
||||
|
||||
export function startStatsServer(config: StatsServerConfig): { close: () => void } {
|
||||
export async function startStatsServerWithRuntime(
|
||||
config: StatsServerConfig,
|
||||
runtime: { bunServe: BunServe | null },
|
||||
): Promise<StatsServer> {
|
||||
const app = createStatsApp(config.tracker, {
|
||||
staticDir: config.staticDir,
|
||||
knownWordCachePath: config.knownWordCachePath,
|
||||
@@ -144,20 +186,26 @@ export function startStatsServer(config: StatsServerConfig): { close: () => void
|
||||
resolveSentenceSearchHeadwords: config.resolveSentenceSearchHeadwords,
|
||||
});
|
||||
|
||||
const bunRuntime = globalThis as typeof globalThis & {
|
||||
Bun?: {
|
||||
serve?: (options: { fetch: (typeof app)['fetch']; port: number; hostname: string }) => {
|
||||
stop: () => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (bunRuntime.Bun?.serve) {
|
||||
const server = bunRuntime.Bun.serve({
|
||||
if (runtime.bunServe) {
|
||||
const server = runtime.bunServe({
|
||||
fetch: app.fetch,
|
||||
port: config.port,
|
||||
hostname: '127.0.0.1',
|
||||
});
|
||||
return { close: () => server.stop() };
|
||||
let closePromise: Promise<void> | null = null;
|
||||
return Promise.resolve({
|
||||
close: () => {
|
||||
closePromise ??= Promise.resolve().then(() => server.stop());
|
||||
return closePromise;
|
||||
},
|
||||
});
|
||||
}
|
||||
return startNodeHttpServer(app, config);
|
||||
}
|
||||
|
||||
export function startStatsServer(config: StatsServerConfig): Promise<StatsServer> {
|
||||
const bunRuntime = globalThis as typeof globalThis & {
|
||||
Bun?: { serve?: BunServe };
|
||||
};
|
||||
return startStatsServerWithRuntime(config, { bunServe: bunRuntime.Bun?.serve ?? null });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user