feature: execute load()
/unload()
methods found in _pre.ts
files
This commit is contained in:
parent
df8291bfc7
commit
046617bc4f
7 changed files with 116 additions and 62 deletions
|
@ -105,6 +105,11 @@ session, perhaps adding it to the `meta` data that will be passed to the `GET`
|
||||||
handler itself. If there is no session, however, it should return an HTTP `Response`
|
handler itself. If there is no session, however, it should return an HTTP `Response`
|
||||||
object indicating permission is denied or similar.
|
object indicating permission is denied or similar.
|
||||||
|
|
||||||
|
#### _pre.ts files
|
||||||
|
|
||||||
|
Any `_pre.ts` files found under the root that export `.load()` and/or `.unload()` methods
|
||||||
|
will be loaded and those functions will be called at server startup/shutdown, respectively.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] reload typescript if it is modified on disk
|
- [ ] reload typescript if it is modified on disk
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@andyburke/serverus",
|
"name": "@andyburke/serverus",
|
||||||
"description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.",
|
"description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.",
|
||||||
"version": "0.11.5",
|
"version": "0.12.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./serverus.ts",
|
".": "./serverus.ts",
|
||||||
|
|
|
@ -34,8 +34,7 @@ type ROUTE_HANDLER_RECORD = {
|
||||||
module?: ROUTE_HANDLER;
|
module?: ROUTE_HANDLER;
|
||||||
};
|
};
|
||||||
const routes: ROUTE_HANDLER_RECORD[] = [];
|
const routes: ROUTE_HANDLER_RECORD[] = [];
|
||||||
let loading: boolean = false;
|
const unloaders: (() => Promise<void> | void)[] = [];
|
||||||
let all_routes_loaded: boolean = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles requests for which there are typescript files in the root tree.
|
* Handles requests for which there are typescript files in the root tree.
|
||||||
|
@ -48,57 +47,6 @@ let all_routes_loaded: boolean = false;
|
||||||
* @returns Either a response (a handler for the request path and method was found) or undefined if unhandled.
|
* @returns Either a response (a handler for the request path and method was found) or undefined if unhandled.
|
||||||
*/
|
*/
|
||||||
export default async function handle_typescript(request: Request): Promise<Response | undefined> {
|
export default async function handle_typescript(request: Request): Promise<Response | undefined> {
|
||||||
if (!all_routes_loaded) {
|
|
||||||
if (!loading) {
|
|
||||||
loading = true;
|
|
||||||
|
|
||||||
const root_directory = path.resolve(Deno.cwd());
|
|
||||||
|
|
||||||
const imports: ROUTE_HANDLER_RECORD[] = [];
|
|
||||||
for await (
|
|
||||||
const entry of walk(root_directory, {
|
|
||||||
exts: ['.ts'],
|
|
||||||
skip: [/\.test\.ts$/]
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
if (entry.isFile) {
|
|
||||||
const relative_path = entry.path.substring(root_directory.length);
|
|
||||||
const route_path = relative_path
|
|
||||||
.replace(/\.ts$/, '')
|
|
||||||
//.replace(/\[(\w+)\]/g, ':$1')
|
|
||||||
.replace(/\/index$/, '')
|
|
||||||
.replace(/___/g, ':') || // required for windows, uncivilized OS that it is
|
|
||||||
'/';
|
|
||||||
|
|
||||||
const route_pattern = new URLPattern({ pathname: route_path });
|
|
||||||
|
|
||||||
const import_path = new URL('file://' + entry.path, import.meta.url).toString();
|
|
||||||
|
|
||||||
imports.push({
|
|
||||||
route_path,
|
|
||||||
route_pattern,
|
|
||||||
import_path
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to sort imports such that they're registered like:
|
|
||||||
// /permissions/test
|
|
||||||
// /api/echo/hi
|
|
||||||
// /api/echo/:input
|
|
||||||
//
|
|
||||||
// we want paths with parameters to sort later than paths without
|
|
||||||
routes.push(...imports.sort((lhs, rhs) => rhs.route_path.localeCompare(lhs.route_path)));
|
|
||||||
|
|
||||||
all_routes_loaded = true;
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
await delay(10);
|
|
||||||
} while (!all_routes_loaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const route_record of routes) {
|
for (const route_record of routes) {
|
||||||
const match = route_record.route_pattern.exec(request.url.replace(/\/$/, ''));
|
const match = route_record.route_pattern.exec(request.url.replace(/\/$/, ''));
|
||||||
if (match) {
|
if (match) {
|
||||||
|
@ -142,8 +90,64 @@ export default async function handle_typescript(request: Request): Promise<Respo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unload(): void {
|
export async function load(): Promise<void> {
|
||||||
loading = false;
|
const root_directory = path.resolve(Deno.cwd());
|
||||||
routes.splice(0, routes.length);
|
|
||||||
all_routes_loaded = false;
|
const imports: ROUTE_HANDLER_RECORD[] = [];
|
||||||
|
for await (
|
||||||
|
const entry of walk(root_directory, {
|
||||||
|
exts: ['.ts'],
|
||||||
|
skip: [/\.test\.ts$/]
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
if (entry.isFile) {
|
||||||
|
const import_path = new URL('file://' + entry.path, import.meta.url).toString();
|
||||||
|
|
||||||
|
const filename = path.basename(entry.path);
|
||||||
|
if (filename === '_pre.ts') {
|
||||||
|
const preloader = await import(import_path);
|
||||||
|
if (typeof preloader.load === 'function') {
|
||||||
|
await preloader.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof preloader.unload === 'function') {
|
||||||
|
unloaders.push(preloader.unload);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relative_path = entry.path.substring(root_directory.length);
|
||||||
|
const route_path = relative_path
|
||||||
|
.replace(/\.ts$/, '')
|
||||||
|
//.replace(/\[(\w+)\]/g, ':$1')
|
||||||
|
.replace(/\/index$/, '')
|
||||||
|
.replace(/___/g, ':') || // required for windows, uncivilized OS that it is
|
||||||
|
'/';
|
||||||
|
|
||||||
|
const route_pattern = new URLPattern({ pathname: route_path });
|
||||||
|
|
||||||
|
imports.push({
|
||||||
|
route_path,
|
||||||
|
route_pattern,
|
||||||
|
import_path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to sort imports such that they're registered like:
|
||||||
|
// /permissions/test
|
||||||
|
// /api/echo/hi
|
||||||
|
// /api/echo/:input
|
||||||
|
//
|
||||||
|
// we want paths with parameters to sort later than paths without
|
||||||
|
routes.push(...imports.sort((lhs, rhs) => rhs.route_path.localeCompare(lhs.route_path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unload(): Promise<void> {
|
||||||
|
for (const unloader of unloaders) {
|
||||||
|
await unloader();
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.splice(0, routes.length);
|
||||||
|
unloaders.splice(0, unloaders.length);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ type LOGGER = (request: Request, response: Response, processing_time: number) =>
|
||||||
*/
|
*/
|
||||||
interface HANDLER_MODULE {
|
interface HANDLER_MODULE {
|
||||||
default: HANDLER;
|
default: HANDLER;
|
||||||
|
load?: () => void | Promise<void>;
|
||||||
unload?: () => void | Promise<void>;
|
unload?: () => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +147,10 @@ export class SERVER {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handlers.push(handler_module);
|
this.handlers.push(handler_module);
|
||||||
|
|
||||||
|
if (handler_module.load) {
|
||||||
|
await handler_module.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Deno.errors.NotFound) {
|
if (error instanceof Deno.errors.NotFound) {
|
||||||
|
|
|
@ -562,10 +562,7 @@ Deno.test({
|
||||||
);
|
);
|
||||||
|
|
||||||
const get_response = await fetch(
|
const get_response = await fetch(
|
||||||
`http://${test_server_info.hostname}:${test_server_info.port}/files/test_put_upload_that_should_not_fail.txt`,
|
`http://${test_server_info.hostname}:${test_server_info.port}/files/test_put_upload_that_should_not_fail.txt`
|
||||||
{
|
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
asserts.assert(get_response.ok);
|
asserts.assert(get_response.ok);
|
||||||
|
|
36
tests/10_test_preloaders.test.ts
Normal file
36
tests/10_test_preloaders.test.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as asserts from '@std/assert';
|
||||||
|
import { EPHEMERAL_SERVER, get_ephemeral_listen_server } from './helpers.ts';
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'check that _preload.ts files work',
|
||||||
|
permissions: {
|
||||||
|
env: true,
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
net: true
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||||
|
const cwd = Deno.cwd();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Deno.chdir('./tests/www');
|
||||||
|
test_server_info = await get_ephemeral_listen_server();
|
||||||
|
|
||||||
|
const preloader_env_setting = Deno.env.get('SERVERUS_PRELOADED_TEST');
|
||||||
|
asserts.assertEquals(preloader_env_setting, 'true');
|
||||||
|
|
||||||
|
await test_server_info.server.stop();
|
||||||
|
|
||||||
|
const preloader_env_setting_after_unload = Deno.env.get('SERVERUS_PRELOADED_TEST');
|
||||||
|
asserts.assertEquals(preloader_env_setting_after_unload, undefined);
|
||||||
|
|
||||||
|
test_server_info = null;
|
||||||
|
} finally {
|
||||||
|
Deno.chdir(cwd);
|
||||||
|
if (test_server_info) {
|
||||||
|
await test_server_info?.server?.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
7
tests/www/_pre.ts
Normal file
7
tests/www/_pre.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function load() {
|
||||||
|
Deno.env.set('SERVERUS_PRELOADED_TEST', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unload() {
|
||||||
|
Deno.env.delete('SERVERUS_PRELOADED_TEST');
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue