| 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"); |
| } |
|
|
| |
| |
| |
| |
| |
| 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 => { |
| 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`); |
| }); |
| 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 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`); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| #requestProcessing(ctx: any, routeFn: Function): Promise<any> { |
| 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(); |