"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startup = startup; const tslib_1 = require("tslib"); /// require("./polyfill"); const http_1 = require("http"); const path_1 = require("path"); const koa_1 = tslib_1.__importDefault(require("koa")); const koa_router_1 = tslib_1.__importDefault(require("koa-router")); const koa_body_1 = tslib_1.__importDefault(require("koa-body")); const oak_backend_base_1 = require("oak-backend-base"); const types_1 = require("oak-domain/lib/types"); const cluster_adapter_1 = require("@socket.io/cluster-adapter"); const sticky_1 = require("@socket.io/sticky"); const socket_io_1 = require("socket.io"); const DATA_SUBSCRIBE_NAMESPACE = '/dsn'; const SOCKET_NAMESPACE = '/sn'; const SERVER_SUBSCRIBER_NAMESPACE = process.env.OAK_SSUB_NAMESPACE || '/ssub'; const ExceptionMask = '内部不可知错误'; function concat(...paths) { return paths.reduce((prev, current) => { if (current.startsWith('/')) { return `${prev}${current}`; } return `${prev}/${current}`; }); } async function startup(path, connector, omitWatchers, omitTimers, routine) { const serverConfiguration = require((0, path_1.join)(path, 'lib', 'configuration', 'server')).default; const corsHeaders = [ 'Content-Type', 'Content-Length', 'Authorization', 'Accept', 'X-Requested-With', ]; const corsMethods = ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS']; const koa = new koa_1.default(); // socket const httpServer = (0, http_1.createServer)(koa.callback()); const socketOption = { path: connector.getSocketPath(), cors: ['development', 'staging'].includes(process.env.NODE_ENV) ? { origin: '*', allowedHeaders: corsHeaders.concat(connector.getCorsHeader()), } : serverConfiguration.cors ? { origin: serverConfiguration.cors.origin, //socket.io配置cors origin是支持数组和字符串 allowedHeaders: [ ...corsHeaders.concat(connector.getCorsHeader()), ...(serverConfiguration.cors.headers || []), ], } : undefined, }; const io = new socket_io_1.Server(httpServer, socketOption); const clusterInfo = (0, oak_backend_base_1.getClusterInfo)(); if (clusterInfo.usingCluster) { // 目前只支持单物理结点的pm2模式 // pm2环境下要接入clusterAdapter // https://socket.io/zh-CN/docs/v4/pm2/ io.adapter((0, cluster_adapter_1.createAdapter)()); (0, sticky_1.setupWorker)(io); console.log(`以集群模式启动,实例总数『${clusterInfo.instanceCount}』,当前实例号『${clusterInfo.instanceId}』`); } else { console.log('以单实例模式启动'); } const appLoader = clusterInfo.usingCluster ? new oak_backend_base_1.ClusterAppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE), connector.getSocketPath()) : new oak_backend_base_1.AppLoader(path, io.of(DATA_SUBSCRIBE_NAMESPACE), io.of(SOCKET_NAMESPACE), io.of(SERVER_SUBSCRIBER_NAMESPACE)); await appLoader.mount(); await appLoader.execStartRoutines(); if (routine) { // 如果传入了routine,执行完成后就结束 await appLoader.execRoutine(routine); return; } // 否则启动服务器模式 koa.use(async (ctx, next) => { try { await next(); } catch (err) { console.error(err); const { request } = ctx; const exception = err instanceof types_1.OakException ? err : new types_1.OakException(serverConfiguration?.internalExceptionMask || ExceptionMask); const { body } = connector.serializeException(exception, request.headers, request.body); ctx.response.body = body; return; } }); koa.use((0, koa_body_1.default)(Object.assign({ multipart: true, }, serverConfiguration.koaBody))); const router = new koa_router_1.default(); // 如果是开发环境,允许options if (['development', 'staging'].includes(process.env.NODE_ENV)) { koa.use(async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Headers', corsHeaders.concat(connector.getCorsHeader())); ctx.set('Access-Control-Allow-Methods', corsMethods); if (ctx.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } }); } else if (serverConfiguration.cors) { koa.use(async (ctx, next) => { if (serverConfiguration.cors.origin instanceof Array) { // 获取到req.headers.origin格式:https://xxx.xx.com 加上端口号 const origin = ctx.req.headers.origin; if (serverConfiguration.cors.origin.includes(origin)) { ctx.set('Access-Control-Allow-Origin', origin); } } else { ctx.set('Access-Control-Allow-Origin', serverConfiguration.cors.origin); } ctx.set('Access-Control-Allow-Headers', [ ...corsHeaders.concat(connector.getCorsHeader()), ...(serverConfiguration.cors.headers || []), ]); ctx.set('Access-Control-Allow-Methods', serverConfiguration.cors.methods || corsMethods); if (ctx.method == 'OPTIONS') { ctx.body = 200; } else { await next(); } }); } router.post(connector.getRouter(), async (ctx) => { const { request } = ctx; const { contextString, aspectName, data } = connector.parseRequest(request.headers, request.body, request.files); const { result, opRecords, message } = await appLoader.execAspect(aspectName, request.headers, contextString, data); const { body, headers } = await connector.serializeResult(result, opRecords, request.headers, request.body, message); ctx.response.body = body; return; }); // 桥接访问外部资源的入口 router.get(connector.getBridgeRouter(), async (ctx) => { const { request: { querystring }, response, } = ctx; const { url, headers } = connector.parseBridgeRequestQuery(querystring); // headers待处理 const res = await fetch(url); response.body = res.body; return; }); // 外部socket接口 /** * 不用pm2 不用nginx: socket与http同端口 用pm2 不用nginx: socket监听8080 不用pm2 用nginx: nginx映射路径到server端口(手动配置),socket与http同端口(加路径) 用pm2 用nginx: nginx映射路径到8080(手动配置),socket与http同端口(加路径) */ const { nginx, hostname, port } = serverConfiguration; const protocol = nginx?.ssl ? 'https://' : 'http://'; let url = `${protocol}${hostname}`; if (nginx) { if (nginx.port) { url += `:${nginx.port}`; } } else if (clusterInfo.usingCluster) { url += `:${process.env.PM2_PORT || 8080}`; } else { url += `:${port}`; } const subscribeUrl = concat(url, DATA_SUBSCRIBE_NAMESPACE); const socketUrl = concat(url, SOCKET_NAMESPACE); // Example: // import { io } from "socket.io-client"; // const socket = io('https://example.com/order', { // path: '/my-custom-path/', // }); // the Socket instance is attached to the "order" Namespace // the HTTP requests will look like: GET https://example.com/my-custom-path/?EIO=4&transport=polling&t=ML4jUwU // 文档 https://socket.io/docs/v4/client-options/ router.get(connector.getSocketPointRouter(), async (ctx) => { const { response } = ctx; response.body = { socketUrl, path: (nginx?.socketPath ? `/${nginx.socketPath}` : '') + connector.getSocketPath(), subscribeUrl, }; return; }); // 注入所有的endpoints const endpoints = appLoader.getEndpoints(connector.getEndpointRouter()); endpoints.forEach(([name, method, url, fn]) => { router[method](url, async (ctx) => { const { req, request, params } = ctx; const { body, headers, files } = request; try { const result = await fn(params, headers, req, files ? Object.assign({}, body, files) : body); ctx.response.body = result; return; } catch (err) { ctx.response.status = 500; return; } }); }); router.get(connector.getEndpointRouter(), async (ctx) => { ctx.response.body = endpoints; }); koa.use(router.routes()); koa.on('error', (err) => { console.error(err); throw err; }); httpServer.listen(serverConfiguration.port, () => { console.log(`server will listen on port ${serverConfiguration.port}`); }); if (!omitWatchers) { appLoader.startWatchers(); } if (!omitTimers) { appLoader.startTimers(); } process.on('SIGINT', async () => { await appLoader.unmount(); process.exit(0); }); const shutdown = async () => { await httpServer.close(); await koa.removeAllListeners(); await appLoader.unmount(); }; return shutdown; }