oak-cli/lib/server/start.js

248 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startup = void 0;
const tslib_1 = require("tslib");
/// <reference path="../typings/polyfill.d.ts" />
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 koa_logger_1 = tslib_1.__importDefault(require("koa-logger"));
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();
// 使用 koa-logger 中间件打印日志
koa.use((0, koa_logger_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,
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;
}
exports.startup = startup;