UNPKG

@midwayjs/koa

Version:

Midway Web Framework for KOA

367 lines 15.6 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MidwayKoaFramework = void 0; const core_1 = require("@midwayjs/core"); const cookies_1 = require("@midwayjs/cookies"); const Router = require("@koa/router"); const koa = require("koa"); const onerror_1 = require("./onerror"); const qs = require("qs"); const querystring = require("querystring"); const utils_1 = require("./utils"); const COOKIES = Symbol('context#cookies'); class KoaControllerGenerator extends core_1.WebControllerGenerator { app; webRouterService; constructor(app, webRouterService) { super(app, webRouterService); this.app = app; this.webRouterService = webRouterService; } createRouter(routerOptions) { const router = new Router(routerOptions); router.prefix(routerOptions.prefix); return router; } generateController(routeInfo) { return this.generateKoaController(routeInfo); } } let MidwayKoaFramework = class MidwayKoaFramework extends core_1.BaseFramework { server; generator; webRouterService; configure() { return this.configService.getConfiguration('koa'); } async applicationInitialize(options) { const appKeys = this.configService.getConfiguration('keys') || this.configurationOptions['keys']; if (!appKeys) { throw new core_1.MidwayConfigMissingError('config.keys'); } const cookieOptions = this.configService.getConfiguration('cookies'); const cookieGetOptions = this.configService.getConfiguration('cookiesExtra.defaultGetOptions'); this.app = new koa({ keys: [].concat(appKeys), proxy: this.configurationOptions.proxy, proxyIpHeader: this.configurationOptions.proxyIpHeader, subdomainOffset: this.configurationOptions.subdomainOffset, maxIpsCount: this.configurationOptions.maxIpsCount, }); Object.defineProperty(this.app.context, 'cookies', { get() { if (!this[COOKIES]) { this[COOKIES] = new cookies_1.Cookies(this, this.app.keys, cookieOptions, cookieGetOptions); } return this[COOKIES]; }, enumerable: true, }); Object.defineProperty(this.app.context, 'locals', { get() { return this.state; }, set(value) { this.state = value; }, }); Object.defineProperty(this.app.context, 'forward', { get() { return async function (url) { const routerService = this.requestContext.get(core_1.MidwayWebRouterService); const matchedUrlRouteInfo = await routerService.getMatchedRouterInfo(url, this.method); if (matchedUrlRouteInfo) { if (matchedUrlRouteInfo.controllerClz) { // normal class controller router const controllerInstance = await this.requestContext.getAsync(matchedUrlRouteInfo.controllerClz); return controllerInstance[matchedUrlRouteInfo.method](this); } else if (typeof matchedUrlRouteInfo.method === 'function') { // dynamic router return matchedUrlRouteInfo.method(this); } } else { throw new core_1.httpError.NotFoundError(`Forward url ${url} Not Found`); } }; }, }); const converter = this.configurationOptions.queryParseMode === 'strict' ? function (value) { return !Array.isArray(value) ? [value] : value; } : this.configurationOptions.queryParseMode === 'first' ? function (value) { return Array.isArray(value) ? value[0] : value; } : undefined; // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; // fix query with array params Object.defineProperty(this.app.request, 'query', { get() { const str = this.querystring; const c = (this._querycache = this._querycache || {}); // find cache if (c[str]) return c[str]; if (self.configurationOptions.queryParseMode) { // use qs module to parse query const parseOptions = { ...self.configurationOptions.queryParseOptions, ...(self.configurationOptions.queryParseMode === 'first' ? { duplicates: 'first' } : {}), }; c[str] = qs.parse(str, parseOptions); } else { // use querystring to parse query by default c[str] = querystring.parse(str); } if (converter) { for (const key in c[str]) { c[str][key] = converter(c[str][key]); } } return c[str]; }, set(value) { this._querycache = this._querycache || {}; this._querycache[this.querystring] = value; }, }); const onerrorConfig = this.configService.getConfiguration('onerror'); (0, onerror_1.setupOnError)(this.app, onerrorConfig, this.logger); // not found middleware const notFound = async (ctx, next) => { await next(); if (!ctx._matchedRoute && ctx.body === undefined) { throw new core_1.httpError.NotFoundError(`${ctx.path} Not Found`); } }; const applyMiddlewares = [notFound]; // versioning middleware const versioningConfig = this.configurationOptions.versioning; if (versioningConfig?.enabled) { applyMiddlewares.push(this.createVersioningMiddleware(versioningConfig)); } // root middleware const midwayRootMiddleware = async (ctx, next) => { this.app.createAnonymousContext(ctx); const traceService = this.applicationContext.get(core_1.MidwayTraceService); const spanName = `${ctx.method} ${ctx.path || '/'}`; const traceMetaResolver = this.configurationOptions?.tracing ?.meta; const traceEnabled = this.configurationOptions?.tracing?.enable !== false; const traceExtractor = this.configurationOptions?.tracing ?.extractor; const traceInjector = this.configurationOptions?.tracing ?.injector; const requestCarrier = typeof traceExtractor === 'function' ? traceExtractor({ ctx, request: ctx.request, response: ctx.response, }) : ctx.headers; const responseCarrier = typeof traceInjector === 'function' ? traceInjector({ ctx, request: ctx.request, response: ctx.response }) : ctx.response.res; await traceService.runWithEntrySpan(spanName, { enable: traceEnabled, carrier: requestCarrier, responseCarrier, attributes: { 'midway.protocol': 'http', }, meta: traceMetaResolver, metaArgs: { ctx, carrier: requestCarrier, request: ctx.request, response: ctx.response, }, }, async () => { await (await this.applyMiddleware(applyMiddlewares))(ctx, next); if (ctx.body === undefined && !ctx.response._explicitStatus && ctx._matchedRoute) { // 如果进了路由,重新赋值,防止 404 ctx.body = undefined; } }); }; this.app.use(midwayRootMiddleware); this.webRouterService = await this.applicationContext.getAsync(core_1.MidwayWebRouterService, [ { globalPrefix: this.configurationOptions.globalPrefix, }, ]); this.generator = new KoaControllerGenerator(this.app, this.webRouterService); this.defineApplicationProperties({ generateController: this.generateController.bind(this), getPort: this.getPort.bind(this), }); // hack use method this.app.originUse = this.app.use; this.app.use = this.app.useMiddleware; } async loadMidwayController() { await this.generator.loadMidwayController(newRouter => { const dispatchFn = newRouter.middleware(); dispatchFn._name = `midwayController(${newRouter?.opts?.prefix || '/'})`; this.app.use(dispatchFn); }); } /** * wrap controller string to middleware function */ generateController(routeInfo) { return this.generator.generateKoaController(routeInfo); } async run() { // load controller await this.loadMidwayController(); // restore use method this.app.use = this.app.originUse; const serverOptions = { ...this.configurationOptions, ...this.configurationOptions.serverOptions, }; // https config if (serverOptions.key && serverOptions.cert) { serverOptions.key = core_1.PathFileUtils.getFileContentSync(serverOptions.key); serverOptions.cert = core_1.PathFileUtils.getFileContentSync(serverOptions.cert); serverOptions.ca = core_1.PathFileUtils.getFileContentSync(serverOptions.ca); process.env.MIDWAY_HTTP_SSL = 'true'; if (serverOptions.http2) { this.server = require('http2').createSecureServer(serverOptions, this.app.callback()); } else { this.server = require('https').createServer(serverOptions, this.app.callback()); } } else { if (serverOptions.http2) { this.server = require('http2').createServer(serverOptions, this.app.callback()); } else { this.server = require('http').createServer(serverOptions, this.app.callback()); } } // register httpServer to applicationContext this.applicationContext.registerObject(core_1.HTTP_SERVER_KEY, this.server); // server timeout if (core_1.Types.isNumber(this.configurationOptions.serverTimeout)) { this.server.setTimeout(this.configurationOptions.serverTimeout); } this.configurationOptions.listenOptions = { port: this.configurationOptions.port, host: this.configurationOptions.hostname, ...this.configurationOptions.listenOptions, }; // set port and listen server let customPort = process.env.MIDWAY_HTTP_PORT || this.configurationOptions.listenOptions.port; if (customPort === 0 || customPort === '0') { customPort = await (0, utils_1.getFreePort)(); this.logger.info(`[midway:koa] server has auto-assigned port ${customPort}`); } this.configurationOptions.listenOptions.port = Number(customPort); if (this.configurationOptions.listenOptions.port) { new Promise(resolve => { // 使用 ListenOptions 对象启动服务器 this.server.listen(this.configurationOptions.listenOptions, () => { resolve(); }); // 设置环境变量 process.env.MIDWAY_HTTP_PORT = String(this.configurationOptions.listenOptions.port); }); this.logger.debug(`[midway:koa] server is listening on port ${customPort}`); } } async beforeStop() { if (this.server) { new Promise(resolve => { this.server.close(resolve); process.env.MIDWAY_HTTP_PORT = ''; }); this.logger.debug('[midway:koa] server is stopped!'); } } getFrameworkName() { return 'koa'; } getServer() { return this.server; } getPort() { return process.env.MIDWAY_HTTP_PORT; } useMiddleware(Middleware) { this.middlewareManager.insertLast(Middleware); } useFilter(Filter) { this.filterManager.useFilter(Filter); } createVersioningMiddleware(config) { return async (ctx, next) => { // 提取版本信息 const version = this.extractVersion(ctx, config); ctx.apiVersion = version; // 对于 URI 版本控制,重写路径 if (config.type === 'URI' && version) { const versionPrefix = `/${config.prefix || 'v'}${version}`; if (ctx.path.startsWith(versionPrefix)) { ctx.originalPath = ctx.path; ctx.path = ctx.path.replace(versionPrefix, '') || '/'; } } await next(); }; } extractVersion(ctx, config) { // 自定义提取函数优先 if (config.extractVersionFn) { return config.extractVersionFn(ctx); } const type = config.type || 'URI'; switch (type) { case 'HEADER': { const headerName = config.header || 'x-api-version'; const headerValue = ctx.headers[headerName]; if (typeof headerValue === 'string') { return headerValue.replace(/^v/, ''); } return undefined; } case 'MEDIA_TYPE': { const accept = ctx.headers.accept; const paramName = config.mediaTypeParam || 'version'; const match = accept?.match(new RegExp(`${paramName}=(\\d+)`)); return match ? match[1] : undefined; } case 'URI': { const prefix = config.prefix || 'v'; const uriMatch = ctx.path.match(new RegExp(`^/${prefix}(\\d+)`)); return uriMatch ? uriMatch[1] : undefined; } default: return config.defaultVersion; } } }; exports.MidwayKoaFramework = MidwayKoaFramework; exports.MidwayKoaFramework = MidwayKoaFramework = __decorate([ (0, core_1.Framework)() ], MidwayKoaFramework); //# sourceMappingURL=framework.js.map