feature: allow for static file uploads and deletions

feature: add HEAD and OPTIONS support to static files
This commit is contained in:
Andy Burke 2025-08-01 20:11:17 -07:00
parent 582636ab5a
commit 3ef936d2d6
7 changed files with 900 additions and 120 deletions

105
server.ts
View file

@ -10,20 +10,26 @@ const EVENTS_TO_SHUTDOWN_ON: Deno.Signal[] = ['SIGTERM', 'SIGINT'];
const DEFAULT_HANDLER_DIRECTORIES = [import.meta.resolve('./handlers')];
/** A `HANDLER` must take a `Request` and return a `Response` if it can handle it. */
type HANDLER = (request: Request) => Promise<Response | null | undefined> | Response | null | undefined;
/**
* @type HANDLER Takes a `Request` and returns a `Response` if it can properly handle the request.
*/
type HANDLER = (request: Request, server: SERVER) => Promise<Response | null | undefined> | Response | null | undefined;
/** A `LOGGER` must take a `Request`, a `Response`, and a `processing_time` and log it. */
/**
* @type LOGGER Takes a `Request`, `Response`, and a `processing_time` in ms to be logged.
*/
type LOGGER = (request: Request, response: Response, processing_time: number) => void | Promise<void>;
/** A `HANDLER_MODULE` must export a default method and may export an unload method to be called at shutdown. */
/**
* @interface HANDLER_MODULE Handler modules should export a default method (handler) and an optional `unload` method to be called at shutdown.
*/
interface HANDLER_MODULE {
default: HANDLER;
unload?: () => void | Promise<void>;
}
/**
* Interface defining the configuration for a serverus server
* @type SERVER_OPTIONS Specifies options for creating the SERVERUS server.
*
* @property {string} [hostname='localhost'] - hostname to bind to
* @property {number} [port=8000] - port to bind to
@ -51,7 +57,7 @@ export const DEFAULT_SERVER_OPTIONS: SERVER_OPTIONS = {
};
/**
* Default logger
* @method LOG_REQUEST Default request logger.
*
* @param {Request} request - the incoming request
* @param {Response} response - the outgoing response
@ -71,17 +77,20 @@ function LOG_REQUEST(request: Request, response: Response, time: number) {
}
/**
* serverus SERVER
*
* Loads all handlers found in the [semi-]colon separated list of directories in
* @class SERVER Loads all handlers found in the [semi-]colon separated list of directories in `SERVERUS_ROOT`.
*/
export class SERVER {
private options: SERVER_OPTIONS;
private server: Deno.HttpServer | undefined;
private controller: AbortController | undefined;
private shutdown_binding: (() => void) | undefined;
private handlers: HANDLER_MODULE[];
private original_directory: string | undefined;
private event_listeners: Record<string, []>;
/**
* @member handlers The HANDLER_MODULEs loaded for this server.
*/
public handlers: HANDLER_MODULE[];
/**
* @param {SERVER_OPTIONS} (optional) options to configure the server
@ -92,6 +101,7 @@ export class SERVER {
...(options ?? {})
};
this.handlers = [];
this.event_listeners = {};
}
/**
@ -163,7 +173,7 @@ export class SERVER {
: (this.options.logging ? LOG_REQUEST : undefined);
for (const handler_module of this.handlers) {
const response = await handler_module.default(request);
const response = await handler_module.default(request, this);
if (response) {
logger?.(request, response, Date.now() - request_time);
return response;
@ -189,6 +199,8 @@ export class SERVER {
// Deno.watchFs;
// }
this.emit('started', {});
return this;
}
@ -196,6 +208,8 @@ export class SERVER {
* Stop the server
*/
public async stop(): Promise<void> {
this.emit('stopping', {});
if (this.server) {
this.server.finished.finally(() => {
if (this.shutdown_binding) {
@ -226,5 +240,74 @@ export class SERVER {
}
}
this.handlers = [];
this.emit('stopped', {});
}
/**
* Add an event listener.
*
* @param {string} event The event to listen for.
* @param {(event_data: any) => void} handler The handler for the event.
*/
public on(event: string, handler: (event_data: any) => void) {
const listeners: ((event: any) => void)[] = this.event_listeners[event] = this.event_listeners[event] ?? [];
if (!listeners.includes(handler)) {
listeners.push(handler);
}
if (Deno.env.get('SERVERUS_LOG_EVENTS')) {
console.dir({
on: {
event,
handler
},
listeners
});
}
}
/**
* Remove an event listener.
*
* @param {string} event The event that was listened to.
* @param {(event_data: any) => void} handler The handler that was registered that should be removed.
*/
public off(event: string, handler: (event_data: any) => void) {
const listeners: ((event: any) => void)[] = this.event_listeners[event] = this.event_listeners[event] ?? [];
if (listeners.includes(handler)) {
listeners.splice(listeners.indexOf(handler), 1);
}
if (Deno.env.get('SERVERUS_LOG_EVENTS')) {
console.dir({
off: {
event: event,
handler
},
listeners
});
}
}
public emit(event_name: string, event_data: any) {
const listeners: ((event: any) => void)[] = this.event_listeners[event_name] = this.event_listeners[event_name] ?? [];
const wildcard_listeners: ((event: any) => void)[] = this.event_listeners['*'] = this.event_listeners['*'] ?? [];
const all_listeners: ((event: any) => void)[] = [...listeners, ...wildcard_listeners];
if (Deno.env.get('SERVERUS_LOG_EVENTS')) {
console.dir({
emitting: {
event_name,
event_data,
listeners,
wildcard_listeners
}
});
}
for (const listener of all_listeners) {
listener(event_data);
}
}
}