Skip to content

Commit 9aaf50f

Browse files
committed
sendfile error handling and more exit handlers
1 parent eaf8e95 commit 9aaf50f

File tree

5 files changed

+38
-21
lines changed

5 files changed

+38
-21
lines changed

packages/events/src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ export class ServerEvents extends EventEmitter<ServerEventsMap> {
2222
eventName: keyof ServerEventsMap | K,
2323
...args: K extends keyof ServerEventsMap ? ServerEventsMap[K] : never
2424
) {
25-
// console.log(eventName);
26-
// console.time(eventName as string);
25+
// the node implementation handles once in the once handler, not in emit
2726
await Promise.all(this.listeners(eventName).map(e => e(...args)));
28-
// console.timeEnd(eventName as string);
2927
}
3028
}
3129

packages/server/src/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Http2ServerRequest, Http2ServerResponse } from "http2";
88
import type { ServerRequest } from "./StateObject";
99
import { Z2, zod as z } from "./Z2";
1010
import { dump } from "wtfnode";
11+
import { caughtPromise } from "./utils";
1112

1213
export * from "./listeners";
1314
export * from "./router";
@@ -28,12 +29,7 @@ export async function startup() {
2829

2930
}
3031

31-
function caughtPromise<F extends (...args: any) => any>(
32-
wrapped: F,
33-
onrejected: (reason: any) => ReturnType<F>
34-
): (...args: Parameters<F>) => ReturnType<F> {
35-
return (...args: Parameters<F>) => wrapped(...args).catch(onrejected);
36-
}
32+
3733

3834

3935
let exiting = false;

packages/server/src/listeners.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { existsSync, readFileSync } from 'node:fs';
22
import { ok } from "node:assert";
33
import { createServer, IncomingMessage, Server, ServerResponse } from "node:http";
4-
import { createSecureServer, Http2SecureServer, Http2ServerRequest, Http2ServerResponse, SecureServerOptions } from "node:http2";
4+
import { createSecureServer, Http2SecureServer, Http2ServerRequest, Http2ServerResponse, Http2Session, SecureServerOptions } from "node:http2";
55
import { Router } from "./router";
66
import { serverEvents } from '@tiddlywiki/events';
77
import { GenericRequest, GenericResponse } from './streamer';
@@ -23,6 +23,7 @@ export class ListenerBase {
2323
) => {
2424
this.handleRequest(req, res);
2525
});
26+
2627
this.server.on('error', (error: NodeJS.ErrnoException) => {
2728

2829
if (error.syscall !== 'listen') {
@@ -79,7 +80,11 @@ export class ListenerHTTPS extends ListenerBase {
7980
return { key, cert, allowHTTP1: true, };
8081
})();
8182
super(createSecureServer(options), router, bindInfo, config);
82-
83+
this.server.on("session", (session: Http2Session) => {
84+
const closeSession = () => { session.close(); }
85+
serverEvents.on("exit", closeSession);
86+
session.on("close", () => { serverEvents.off("exit", closeSession); })
87+
});
8388
}
8489

8590
}

packages/server/src/streamer.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as http2 from 'node:http2';
22
import send, { SendOptions } from 'send';
33
import { Readable } from 'stream';
44
import { IncomingMessage, ServerResponse, IncomingHttpHeaders as NodeIncomingHeaders, OutgoingHttpHeaders, OutgoingHttpHeader } from 'node:http';
5-
import { is } from './utils';
5+
import { caughtPromise, is } from './utils';
66
import { createReadStream } from 'node:fs';
77
import { Writable } from 'node:stream';
88
import Debug from "debug";
@@ -361,19 +361,32 @@ export class Streamer {
361361
});
362362
return new Promise<typeof STREAM_ENDED>((resolve, reject) => {
363363

364-
sender.on("error", (err) => Promise.resolve().then(async (): Promise<typeof STREAM_ENDED> => {
364+
sender.on("error", caughtPromise(async (err) => {
365+
interface SendError {
366+
errno: -2,
367+
code: 'ENOENT',
368+
syscall: 'stat',
369+
/** The absolute file path that was resolved and didn't exist */
370+
path: string,
371+
expose: boolean,
372+
/** status and statusCode are identical */
373+
statusCode: number,
374+
/** status and statusCode are identical */
375+
status: number
376+
}
365377
if (err === 404 || err?.statusCode === 404) {
366-
return (await on404?.()) ?? this.sendEmpty(404);
378+
if (on404) on404();
379+
else this.sendEmpty(404);
367380
} else {
368381
console.log(err);
369-
throw this.sendEmpty(500);
382+
this.sendEmpty(500);
370383
}
371-
}).then(resolve, reject));
384+
}, reject));
372385

373-
sender.on("directory", () => Promise.resolve().then(async (): Promise<typeof STREAM_ENDED> => {
374-
return (await onDir?.())
375-
?? this.sendEmpty(404, { "x-reason": "Directory listing not allowed" })
376-
}).then(resolve, reject));
386+
sender.on("directory", caughtPromise(async () => {
387+
if (onDir) onDir();
388+
else this.sendEmpty(404, { "x-reason": "Directory listing not allowed" })
389+
}, reject));
377390

378391
sender.on("stream", (fileStream) => {
379392
this.compressor.beforeWriteHead();

packages/server/src/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,9 @@ export declare interface JsonArray extends Array<JsonValue> { }
156156
export declare type JsonObject = { [Key in string]?: JsonValue; };
157157
export declare type JsonValue = string | number | boolean | JsonObject | JsonArray | null | Date;
158158

159-
159+
export function caughtPromise<F extends (...args: any) => any, C extends (reason: any) => any>(
160+
wrapped: F,
161+
onrejected: C
162+
): (...args: Parameters<F>) => ReturnType<F | C> {
163+
return (...args: Parameters<F>) => wrapped(...args).catch(onrejected);
164+
}

0 commit comments

Comments
 (0)