diff --git a/src/server.test.ts b/src/server.test.ts index f296319..ca64c93 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -40,11 +40,14 @@ test('Reads and writes from the file system', async () => { } // Store #1: Edge access + const server1Ops: string[] = [] const directory1 = await tmp.dir() const server1 = new BlobsServer({ directory: directory1.path, + onRequest: ({ type }) => server1Ops.push(type), token, }) + const { port: port1 } = await server1.start() const store1 = getStore({ edgeURL: `http://localhost:${port1}`, @@ -110,6 +113,28 @@ test('Reads and writes from the file system', async () => { expect(list3.directories).toEqual([]) } + expect(server1Ops).toEqual([ + 'list', + 'set', + 'get', + 'set', + 'get', + 'list', + 'set', + 'get', + 'get', + 'get', + 'getMetadata', + 'getMetadata', + 'get', + 'getMetadata', + 'delete', + 'get', + 'getMetadata', + 'get', + 'list', + ]) + await server1.stop() await fs.rm(directory1.path, { force: true, recursive: true }) diff --git a/src/server.ts b/src/server.ts index fa13b7b..bc366fe 100644 --- a/src/server.ts +++ b/src/server.ts @@ -6,11 +6,20 @@ import { dirname, join, relative, resolve, sep } from 'node:path' import { ListResponse } from './backend/list.ts' import { decodeMetadata, encodeMetadata, METADATA_HEADER_INTERNAL } from './metadata.ts' +import { HTTPMethod } from './types.ts' import { isNodeError, Logger } from './util.ts' const API_URL_PATH = /\/api\/v1\/sites\/(?[^/]+)\/blobs\/?(?[^?]*)/ const DEFAULT_STORE = 'production' +export enum Operation { + DELETE = 'delete', + GET = 'get', + GET_METADATA = 'getMetadata', + LIST = 'list', + SET = 'set', +} + interface BlobsServerOptions { /** * Whether debug-level information should be logged, such as internal errors @@ -28,6 +37,11 @@ interface BlobsServerOptions { */ logger?: Logger + /** + * Callback function to be called on every request. + */ + onRequest?: (parameters: { type: Operation }) => void + /** * Port to run the server on. Defaults to a random port. */ @@ -45,16 +59,22 @@ export class BlobsServer { private debug: boolean private directory: string private logger: Logger + private onRequest: (parameters: { type: Operation }) => void private port: number private server?: http.Server private token?: string private tokenHash: string - constructor({ debug, directory, logger, port, token }: BlobsServerOptions) { + constructor({ debug, directory, logger, onRequest, port, token }: BlobsServerOptions) { this.address = '' this.debug = debug === true this.directory = directory this.logger = logger ?? console.log + this.onRequest = + onRequest ?? + (() => { + // no-op + }) this.port = port || 0 this.token = token this.tokenHash = createHmac('sha256', Math.random.toString()) @@ -124,6 +144,8 @@ export class BlobsServer { return this.list({ dataPath, metadataPath, rootPath, req, res, url }) } + this.onRequest({ type: Operation.GET }) + const headers: Record = {} try { @@ -193,6 +215,8 @@ export class BlobsServer { res: http.ServerResponse url: URL }) { + this.onRequest({ type: Operation.LIST }) + const { dataPath, rootPath, req, res, url } = options const directories = url.searchParams.get('directories') === 'true' const prefix = url.searchParams.get('prefix') ?? '' @@ -295,17 +319,26 @@ export class BlobsServer { return this.sendResponse(req, res, 403) } - switch (req.method) { - case 'DELETE': + switch (req.method?.toLowerCase()) { + case HTTPMethod.DELETE: { + this.onRequest({ type: Operation.DELETE }) + return this.delete(req, res) + } - case 'GET': + case HTTPMethod.GET: { return this.get(req, res) + } + + case HTTPMethod.PUT: { + this.onRequest({ type: Operation.SET }) - case 'PUT': return this.put(req, res) + } + + case HTTPMethod.HEAD: { + this.onRequest({ type: Operation.GET_METADATA }) - case 'HEAD': { return this.head(req, res) }