UNPKG

@yucom/rest-server

Version:
237 lines (206 loc) 9.15 kB
import logger from '@yucom/log'; import Express from 'express'; import { ProxyPathHandler, ProxyTargetType } from './path-proxy'; import { Server } from 'http'; let serverLog = logger.create('server-app:request-handler'); type Request = Express.Request; type Response = Express.Response; type NextFunction = Express.NextFunction; type ExpressRouter = Express.Router; type ListHandler = (...params: string[]) => (Promise<any[]> | any[]); type GetHandler = (...params: string[]) => any; type RemoveHandler = (...params: string[]) => any; type CreateHandler = (body: object, ...params: string[]) => any; type ReplaceHandler = (body: object, ...params: string[]) => any; type UpdateHandler = (body: object, ...params: string[]) => any; type InvokeHandler = (body: object, ...params: string[]) => any; type InterceptHandler = (next: NextFunction) => any; type OperationHandler = ListHandler | GetHandler | RemoveHandler | CreateHandler | UpdateHandler | InvokeHandler | InterceptHandler; type InternalHandler = (body: object, ...params: string[]) => object; type validOperation = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'use'; type RequestContext = { request: Request; response: Response; args: any; headers: any; cookies: any; }; type HandlerRegister<T extends OperationHandler> = (paths: string[], handler: T | ReturnType<T>) => any; interface PathHandler<T extends OperationHandler> extends ProxyTargetType { [key: string]: PathHandler<T>; (handler: T | ReturnType<T>): void; } function asHandler<T extends OperationHandler>(handler: T | ReturnType<T>): T { if (typeof(handler) === 'function') { return handler; } else { return <T>(() => handler); } } class RequestHandler { public list: PathHandler<ListHandler>; public get: PathHandler<GetHandler>; public create: PathHandler<CreateHandler>; public replace: PathHandler<ReplaceHandler>; public update: PathHandler<UpdateHandler>; public remove: PathHandler<RemoveHandler>; public invoke: PathHandler<InvokeHandler>; public intercept: PathHandler<InterceptHandler>; public static: PathHandler<() => string>; private readyPromise: Promise<void> = undefined; private server: Server = undefined; constructor(readonly express: Express.Express, private router: ExpressRouter, private pathsRecord: any) { this.list = this.initPathHanlder((paths: string[], response: ListHandler | any[]) => { let handler = asHandler<ListHandler>(response); this.registerHandler( 'get', paths, 200, async function(_, ...params: string[]) { return await handler.call(this, ...params); }); } ); this.get = this.initPathHanlder((paths: string[], response: GetHandler | any) => { let handler = asHandler(response); this.registerHandler( 'get', paths, 200, async function(_, ...params: string[]) { return await handler.call(this, ...params); }); }); this.create = this.initPathHanlder((paths: string[], response: CreateHandler | any) => { let handler = asHandler(response); this.registerHandler( 'post', paths, 201, async function(body: object, ...params: string[]) { return await handler.call(this, body, ...params); }); }); this.replace = this.initPathHanlder((paths: string[], response: ReplaceHandler | any) => { let handler = asHandler(response); this.registerHandler( 'put', paths, 201, async function(body: object, ...params: string[]) { return await handler.call(this, body, ...params); }); }); this.remove = this.initPathHanlder((paths: string[], response: RemoveHandler | any) => { let handler = asHandler(response); this.registerHandler( 'delete', paths, 204, async function(_, ...params: string[]) { return await handler.call(this, ...params); }); }); this.update = this.initPathHanlder((paths: string[], response: UpdateHandler | any) => { let handler = asHandler(response); this.registerHandler( 'patch', paths, 201, async function(body: object, ...params: string[]) { return await handler.call(this, body, ...params); }); }); this.invoke = this.initPathHanlder((paths: string[], response: InvokeHandler | any) => { let handler = asHandler(response); this.registerHandler( 'post', paths, 200, async function(body: object, ...params: string[]) { return await handler.call(this, body, ...params); }); }); this.intercept = this.initPathHanlder((paths: string[], handler: InterceptHandler) => this.registerInterceptor( 'use', paths, async function(next: NextFunction) { handler.call(this, next); })); this.static = this.initPathHanlder((paths: string[], rootFolder: string) => { const expressPath = this.getPath(paths); serverLog.debug(`Static("${expressPath}")`); router.use( expressPath, Express.static(rootFolder, { fallthrough: false, extensions: ['html', 'htm'] }) ); }); } ready() { return this.readyPromise || Promise.reject(new Error('Server not started. Use listen.')); } close() { return new Promise<void>((resolve, reject) => { this.server.close((err) => { if (err) reject(err); else resolve(); }); }); } listen(port: number = 7000) { if (this.server) return Promise.reject(new Error('Cannot restart a server. Not supported.')); this.readyPromise = new Promise(resolve => { this.server = this.express.listen(port, resolve); }); return this.readyPromise; } private getExpressPath = (paths: string[]) => paths.map((p, idx) => p.startsWith('$') ? ':var' + idx : p); private getParams = (paths: string[]) => this.getExpressPath(paths).filter(p => p.startsWith(':')).map(p => p.slice(1)); private getPath = (paths: string[]) => '/' + this.getExpressPath(paths).join('/'); private getParamsValues = (req: Request, params: string[]) => params.map(param => req.params[param]); private getContext = (req: Request, res: Response) => <RequestContext> { request: req , response: res , args: req.query , headers: req.headers , cookies: req.cookies } private initPathHanlder = <T extends OperationHandler>(onExecute: Function) => ProxyPathHandler.create<PathHandler<T>>([], onExecute) private recordPath(path: string, method: validOperation) { serverLog.debug(`Handler(${method.toUpperCase()}, "${path}")`); this.pathsRecord[path] = this.pathsRecord[path] === undefined ? [] : this.pathsRecord[path]; this.pathsRecord[path].push(method); } private registerHandler(method: validOperation, paths: string[], successfulStatus: number, handler: InternalHandler) { const expressPath = this.getPath(paths); const params = this.getParams(paths); this.recordPath(expressPath, method); this.router[method](expressPath, (req: Request, res: Response, next: NextFunction) => { handler.call(this.getContext(req, res), req.body, ...this.getParamsValues(req, params)) .then((result: any) => { serverLog.info( `Request(path="${req.originalUrl}", body=${JSON.stringify(req.body)}) => ` + `Response(status=${successfulStatus}, body=${JSON.stringify(result)})`); res.status(successfulStatus).json({ data: result }); }) .catch(next); }); } private registerInterceptor(method: validOperation, paths: string[], handler: InterceptHandler) { const expressPath = this.getPath(paths); serverLog.debug(`Inteceptor("${expressPath}")`); this.router[method](expressPath, (req: Request, res: Response, next: NextFunction) => handler.call(this.getContext(req, res), next).catch(next) ); } } export { RequestHandler };