import Koa from 'koa'; import KoaRouter from 'koa-router'; import koaRange from 'koa-range'; import koaCors from "koa2-cors"; import koaBody from 'koa-body'; import _ from 'lodash'; import Exception from './exceptions/Exception.ts'; import Request from './request/Request.ts'; import Response from './response/Response.js'; import FailureBody from './response/FailureBody.ts'; import EX from './consts/exceptions.ts'; import logger from './logger.ts'; import config from './config.ts'; class Server { app; router; constructor() { this.app = new Koa(); this.app.use(koaCors()); // 范围请求支持 this.app.use(koaRange); this.router = new KoaRouter({ prefix: config.service.urlPrefix }); // 前置处理异常拦截 this.app.use(async (ctx: any, next: Function) => { if(ctx.request.type === "application/xml" || ctx.request.type === "application/ssml+xml") ctx.req.headers["content-type"] = "text/xml"; try { await next() } catch (err) { logger.error(err); const failureBody = new FailureBody(err); new Response(failureBody).injectTo(ctx); } }); // 载荷解析器支持 this.app.use(koaBody(_.clone(config.system.requestBody))); this.app.on("error", (err: any) => { // 忽略连接重试、中断、管道、取消错误 if (["ECONNRESET", "ECONNABORTED", "EPIPE", "ECANCELED"].includes(err.code)) return; logger.error(err); }); logger.success("Server initialized"); } /** * 附加路由 * * @param routes 路由列表 */ attachRoutes(routes: any[]) { routes.forEach((route: any) => { const prefix = route.prefix || ""; for (let method in route) { if(method === "prefix") continue; if (!_.isObject(route[method])) { logger.warn(`Router ${prefix} ${method} invalid`); continue; } for (let uri in route[method]) { this.router[method](`${prefix}${uri}`, async ctx => { // this.router[method]("hf" + `${prefix}${uri}`, async ctx => { const { request, response } = await this.#requestProcessing(ctx, route[method][uri]); if(response != null && config.system.requestLog) logger.info(`<- ${request.method} ${request.url} ${response.time - request.time}ms`); }); } } logger.info(`Route ${config.service.urlPrefix || ""}${prefix} attached`); // logger.info(`Route ${config.service.urlPrefix || ""}hf${prefix} attached`); }); this.app.use(this.router.routes()); this.app.use((ctx: any) => { const request = new Request(ctx); logger.debug(`-> ${ctx.request.method} ${ctx.request.url} request is not supported - ${request.remoteIP || "unknown"}`); // const failureBody = new FailureBody(new Exception(EX.SYSTEM_NOT_ROUTE_MATCHING, "Request is not supported")); // const response = new Response(failureBody); const message = `[请求有误]: 正确请求为 POST -> /v1/chat/completions,当前请求为 ${ctx.request.method} -> ${ctx.request.url} 请纠正`; logger.warn(message); const failureBody = new FailureBody(new Error(message)); const response = new Response(failureBody); response.injectTo(ctx); if(config.system.requestLog) logger.info(`<- ${request.method} ${request.url} ${response.time - request.time}ms`); }); } /** * 请求处理 * * @param ctx 上下文 * @param routeFn 路由方法 */ #requestProcessing(ctx: any, routeFn: Function): Promise { return new Promise(resolve => { const request = new Request(ctx); try { if(config.system.requestLog) logger.info(`-> ${request.method} ${request.url}`); routeFn(request) .then(response => { try { if(!Response.isInstance(response)) { const _response = new Response(response); _response.injectTo(ctx); return resolve({ request, response: _response }); } response.injectTo(ctx); resolve({ request, response }); } catch(err) { logger.error(err); const failureBody = new FailureBody(err); const response = new Response(failureBody); response.injectTo(ctx); resolve({ request, response }); } }) .catch(err => { try { logger.error(err); const failureBody = new FailureBody(err); const response = new Response(failureBody); response.injectTo(ctx); resolve({ request, response }); } catch(err) { logger.error(err); const failureBody = new FailureBody(err); const response = new Response(failureBody); response.injectTo(ctx); resolve({ request, response }); } }); } catch(err) { logger.error(err); const failureBody = new FailureBody(err); const response = new Response(failureBody); response.injectTo(ctx); resolve({ request, response }); } }); } /** * 监听端口 */ async listen() { const host = config.service.host; const port = config.service.port; await Promise.all([ new Promise((resolve, reject) => { if(host === "0.0.0.0" || host === "localhost" || host === "127.0.0.1") return resolve(null); this.app.listen(port, "localhost", err => { if(err) return reject(err); resolve(null); }); }), new Promise((resolve, reject) => { this.app.listen(port, host, err => { if(err) return reject(err); resolve(null); }); }) ]); logger.success(`Server listening on port ${port} (${host})`); } } export default new Server();