# Web服务
# 启动Web服务
我们使用Corejs内置的ServiceCore创建和管理Web服务。启动Web服务分为两个步骤:
首先,使用Core.ServiceCore创建ServiceCore实例:
const Core = require('node-corejs');
const serviceCore = new Core.ServiceCore();
2
创建ServiceCore时可以指定Web服务的配置对象configs:
configs.id:ServiceCore的ID,用于标识ServiceCore。非必填项,默认值:ServiceCore_${generateRandomString(6, 'uln')};configs.port:Web服务驻留的端口。非必填项,默认值:3000。configs.serverOpt:Web服务的构建配置。非必填项,默认值:{}。configs.baseRoutePath:Web服务的基础请求路径,用于指定统一的请求路径前缀。非必填项,默认值:'/'。比如,当此配置指定为
'/api',且ServiceCore挂载了请求路径规则为'/Test.do'的Handler时,客户端需要请求'/api/Test.do'才能命中此Handler。注意
ServiceCore将自动校正配置对象中指定的基础请求路径:
当基础请求路径不以
'/'开头时,将附加'/'作为前缀。比如:当业务层指定基础请求路径为'api'时将自动被校正为'/api'。当基础请求路径以
'/'结尾时,将删除位于结尾的所有'/'。比如:当业务层指定基础请求路径为'/api//'时将自动被校正为'/api'。
configs.middlewares:全局中间件列表。非必填项,默认值:[]。
然后,使用实例方法start()启动Web服务。此方法接收两个非必填参数:启动配置和回调函数。
serviceCore.start([[options], callBack]);
提示
对于构建配置configs.serverOpt和启动配置options,我们将在构建过程一节中进行详细讨论。
通常,我们在执行start()时使用回调函数处理Web服务的启动结果:
serviceCore.start((error) => {
// 如果error为null则表示Web服务启动成功
if (error) {
// Web服务启动失败
console.log(`服务启动失败: ${error}`);
return;
}
// Web服务启动成功
console.log('服务启动成功');
});
2
3
4
5
6
7
8
9
10
需要注意的是,start()执行成功后将会变更ServiceCore为启动状态,仅允许在关闭状态执行的操作将被拒绝执行:
# 设置请求路径
处理特定请求路径的客户端请求需要结合Handler进行。Handler有独立于ServiceCore的生命周期和处理流程。
客户端请求经过全局中间件管道后,将进入与请求路径匹配的Handler中执行后续处理。
我们实现一个Handler至少需要:
- 指定请求路径规则
- 指定请求方式(比如:GET、POST)对应的处理逻辑
因此,我们在自定义Handler时至少需要:
- 实现一个继承自
Core.Handler的类 - 重写
getRoutePath静态方法,指定请求路径规则 - 重写
[METHOD]Handler实例方法,指定请求方式对应的处理逻辑
注意
指定请求路径规则时,需要重写Handler中的静态方法,即static getRoutePath();而实现某个请求方式的处理逻辑时重写的[METHOD]Handler是实例方法。
另外,[METHOD]Handler不是实际重写的方法名,只是Handler Method的代称,比如:期望处理客户端POST请求时,需要重写Handler中的实例方法postHandler。
接下来,让我们来实现Hello World Handler:
class HelloWorldHandler extends Core.Handler {
// 指定请求路径规则
static getRoutePath() {
return '/HelloWorld.do';
}
// 指定get请求处理
getHandler(req, res, next) {
next('Hello World');
}
}
2
3
4
5
6
7
8
9
10
需要注意的是,Handler必须绑定至ServiceCore才能生效。
所以,我们还应该使用ServiceCore的实例方法bind()将Handler与ServiceCore绑定:
// 实现Hello World Handler
class HelloWorldHandler extends Core.Handler { ... }
// 创建ServiceCore
const serviceCore = new Core.ServiceCore();
// 绑定ServiceCore和Handler
serviceCore.bind([HelloWorldHandler]);
// 启动ServiceCore
serviceCore.start();
2
3
4
5
6
7
8
9
最后,我们使用浏览器打开http://localhost:3000/HelloWorld.do就可以检查实现成果啦!
注意
ServiceCore实例仅允许在处于关闭状态时执行bind()动作,且多次执行bind()时将只保留最后一次绑定的Handler。
# 构建过程
ServiceCore执行实例方法start()时将触发Web服务的实际构建,我们可以通过修改实例属性createServer对构建过程进行定制。
注意
ServiceCore实例仅允许在处于关闭状态时变更其createServer属性。
构建过程中需要包含构建Server和启动Server两个阶段。
ServiceCore的实例属性createServer应根据构建过程内实际逻辑的同步或异步使用Function或AsyncFunction,其参数列表依次为:
options:执行start()时指定的启动配置。app:ServiceCore自动构建的Express实例。configs:ServiceCore实例化时指定的构造参数。ServiceCore在实例化过程中将自动对构造参数中指定的配置进行校正。因此可能与创建ServiceCore时实际指定的构造参数有差异。
callBack:回调函数,参数列表为(error, detail)。ServiceCore将根据此函数回调的信息对构建结果进行仲裁,当回调的
error为null时表示构建成功,此时ServiceCore将被变更为启动状态。说明
我们可以在定制构建过程时,使用期望的
error和detail调用callBack,以控制ServiceCore实例方法start()回调启动结果时的附加信息。在默认的构建过程下,
detail的结构为{ app, server, serverType }:app:ServiceCore自动构建的Express实例。server:支撑Web服务的Server实例。serverType:Server实例的类型,为'http'或'https'。
接下来,我们将结合默认构建过程的实现,来讨论创建ServiceCore时指定的configs.serverOpt和执行start()时指定的options的作用。
默认的构建过程实现如下:
createServer(options, app, configs, callBack) {
// 参数处理
const { port, serverOpt } = configs;
options = Object.assign({}, { port }, options);
// 创建server
let server = null;
let serverType = 'http';
// 当指定了有效的ssl配置时 - 启动https服务器
if (serverOpt && serverOpt.cert && serverOpt.key) {
server = https.createServer(serverOpt, app);
serverType = 'https';
}
// 未指定ssl或配置无效时 - 启动http服务器
else {
server = http.createServer(serverOpt, app);
serverType = 'http';
}
// 启动server
server.on('error', (error) => callBack(error));
server.on('listening', () => callBack(null, { app, server, serverType }));
server.listen(options);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
因此,在创建ServiceCore时,如果指定了有效的configs.serverOpt.key和configs.serverOpt.cert,则在执行start()时将使用https.createServer()创建Server实例,否则使用http.createServer()。
提示
configs.serverOpt将作为创建Server实例的配置参数;执行实例方法start()时指定的启动配置options将作为启动Server实例的配置参数。
具体配置项可以参照NodeJS官方文档中https.createServer()、http.createServer()和server.listen()的相关描述。
# TLS/SSL
如果我们期望启动TLS/SSL模式的Web服务,只需在实例化ServiceCore时指定有效的configs.serverOpt.key和configs.serverOpt.cert即可。
我们在构建过程一节已经了解到,默认的构建行为使用https.createServer()实现Web服务的TLS/SSL,因此:
我们可以通过指定证书和密钥文件路径的方式启动TLS/SSL模式的Web服务。
const SSL_KEY_PATH = './ssl.key';
const SSL_CERT_PATH = './cert.pem';
const serviceCore = new Core.ServiceCore({
serverOpt: {
key: SSL_KEY_PATH,
cert: SSL_CERT_PATH
}
});
2
3
4
5
6
7
8
9
通常,直接指定证书和密钥文件的路径具有很大的局限性(比如:打包导致文件路径产生偏移)。
我们可以使用将证书和密钥装入Buffer的形式进行规避:
const fs = require('fs');
const SSL_KEY_PATH = './ssl.key';
const SSL_CERT_PATH = './cert.pem';
const serviceCore = new Core.ServiceCore({
serverOpt: {
key: fs.readFileSync(SSL_KEY_PATH),
cert: fs.readFileSync(SSL_CERT_PATH)
}
});
2
3
4
5
6
7
8
9
10
# 处理模型

# 全局拦截器
进入ServiceCore的所有客户端流量将被全局拦截器捕获并处理。ServiceCore将在业务层执行实例方法start()时,将全局拦截器使用app.use()挂载至Express中间件列表首位。
我们可以通过修改ServiceCore的实例属性globalIntercaptor以对全局拦截器行为进行定制。
注意
在全局拦截器逻辑结束后,务必使用
next()分发处理流程至后续阶段。ServiceCore实例仅允许在处于关闭状态时变更其
globalIntercaptor属性。ServiceCore将自动捕获全局拦截器根函数维度产生的异常,使之进入错误拦截器处理。(因此,推荐使用
async/await执行异步动作以保证异常抛出)。
ServiceCore的实例属性globalIntercaptor应根据全局拦截器内实际逻辑的同步或异步使用Function或AsyncFunction,其参数列表依次为:
req:客户端请求实例。res:客户端返回实例。next:Express中间件流程控制函数,使用方式可以参考Express官方文档中对于next()的描述。
在全局拦截器中不包含异步逻辑时,我们通常指定为Function类型:
// 指定同步全局拦截器
serviceCore.globalIntercaptor = (req, res, next) => {
// 执行全局拦截器逻辑
// ...
// 拦截器逻辑结束后执行next()分发处理流程至下游链路
next();
};
2
3
4
5
6
7
当全局拦截器中包含异步逻辑时,推荐指定为AsyncFunction类型以使用await指令进行异步操作:
// 指定异步全局拦截器
serviceCore.globalIntercaptor = async (req, res, next) => {
// 执行全局拦截器逻辑
// ...
// 拦截器逻辑结束后执行next()分发处理流程至下游链路
next();
};
2
3
4
5
6
7
接下来,我们将结合默认全局拦截器的实现,来讨论其对后续处理过程产生的影响。
默认全局拦截器的实现如下:
globalInterceptor(req, res, next) {
const requestPath = req.path;
const routePathes = Object.keys(this._handlerMap);
let isHandlerBinded = false;
for (let i = 0; i < routePathes.length; i++) {
const handlerRoutePath = routePathes[i];
if (requestPath.indexOf(this.baseRoutePath) === 0 && requestPath.indexOf(handlerRoutePath) !== -1) {
(isHandlerBinded = true) && next();
break;
}
}
!isHandlerBinded && res.status(404).end();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
因此,如果客户端请求路径未能匹配到Handler,则认为请求无效直接返回404状态码,不再进入后续的全局中间件和Handler阶段。
注意
我们可能在全局中间件中使用泛路径中间件(如:express.static),默认的全局拦截器行为可能导致请求无效。此类场景下,我们可以尝试:
- 将泛路径中间件变更至Handler维度。
- 变更全局拦截器默认行为,放行相关请求。
# 全局中间件
我们把在全局拦截器中被放行的客户端请求称为有效请求,作用于有效请求的中间件便是全局中间件。
提示
全局中间件依赖于Express中间件系统实现,因此兼容Express生态且不支持动态中间件行为。
对于作用于特定请求路径的中间件,我们可以在自定义Handler时根据客户端请求的实际上下文(比如:请求参数):
- 动态指定中间件列表
- 控制中间件执行规则(比如:跳过执行)
全局中间件由创建ServiceCore时的构造参数configs.middlewares配置项指定:
const serviceCore = new Core.ServiceCore({
middlewares: []
});
2
3
ServiceCore在业务层执行实例方法start()时,当挂载全局拦截器完成后,将迭代构造参数configs.middlewares内的中间件,依次使用app.use()挂载至Express中间件列表。
因此,我们可以直接使用Express生态的中间件作为全局中间件:
const bodyParser = require('body-parser');
// 创建body-parser中间件
const jsonParser = bodyParser.json({ limit: 2 * 1024 * 1024 });
const urlEncodedParser = bodyParser.urlencoded({ limit: 2 * 1024 * 1024, extended: true });
// 创建ServiceCore并启动
const serviceCore = new Core.ServiceCore({
middlewares: [jsonParser, urlEncodedParser]
});
serviceCore.start();
2
3
4
5
6
7
8
9
需要注意的是,全局中间件不支持动态中间件。在一些全局中间件逻辑控制的场景中,我们可以使用Middleware Wrapper实现。
接下来,我们将实现一个根据body-parser解析结果进行自定义处理的🌰,解析异常时不再向客户端抛出500状态码,而是使用200状态码向客户端返回异常信息:
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json({ limit: 1 });
// 对原始中间件进行包装,修改默认逻辑
const jsonParserWrapper = (req, res, next) => {
jsonParser(req, res, (error) => { res.status(200).send(error.message) });
}
// 创建ServiceCore并启动
const serviceCore = new Core.ServiceCore({
middlewares: [jsonParserWrapper],
});
serviceCore.start();
2
3
4
5
6
7
8
9
10
11
# 错误拦截器
ServiceCore引入了错误拦截器用于收口请求处理过程中产生的异常。ServiceCore在业务层执行实例方法start()时,当挂载全局拦截器和全局中间件完成后,将错误拦截器使用app.use()挂载至Express中间件列表。
提示
错误拦截器本质上是一个位于Express中间件列表的末位的标准错误中间件,将捕获到以下类型的异常:
- 全局拦截器执行过程中产生的异常
- 全局中间件执行过程中产生的异常
- Handler统一错误处理执行过程中产生的异常
默认的错误拦截器将直接向客户端返回500状态码(即:执行res.status(500).end()),我们可以通过修改ServiceCore的实例属性errorIntercaptor以对错误拦截器逻辑进行定制。
注意
ServiceCore实例仅允许在处于关闭状态时变更其errorIntercaptor属性。
另外,ServiceCore将自动包装错误拦截器为Express标准错误中间件。因此,我们在设置错误拦截器时,函数签名按需指定即可,无需严格保持(error, req, res, next)。
ServiceCore的实例属性errorIntercaptor应根据错误拦截器内实际逻辑的同步或异步使用Function或AsyncFunction,其参数列表依次为:
error:未被捕获的异常。req:客户端请求实例。res:客户端返回实例。next:Express中间件流程控制函数,使用方式可以参考Express官方文档中对于next()的描述。
在错误拦截器中不包含异步逻辑时,我们通常指定为Function类型:
// 指定同步错误拦截器
serviceCore.errorIntercaptor = (error, req, res, next) => {
// 执行错误拦截器逻辑
// ...
};
2
3
4
5
当错误拦截器中包含异步逻辑时,推荐指定为AsyncFunction类型以使用await指令进行异步操作:
// 指定异步全局拦截器
serviceCore.globalIntercaptor = async (error, req, res, next) => {
// 执行全局拦截器逻辑
// ...
};
2
3
4
5
当然,错误拦截器的执行过程中也可能产生异常,默认将触发Express的异常处理逻辑。
对于此类异常,我们通常在自定义构建过程时向Express实例中注入兜底Express错误处理中间件:
const serviceCore = new Core.serviceCore();
const nativeCreateServer = serviceCore._createServer;
serviceCore.createServer = (options, app, configs, callBack) => {
// 注入兜底错误处理中间件(逻辑应保持简单以降低异常产生概率)
app.use((error, req, res, next) => {
res.status(500).send(error.message);
});
nativeCreateServer(options, app, configs, callBack);
}
2
3
4
5
6
7
8
9
# 日志收集
我们可以通过ServiceCore的实例属性logger指定其使用的日志收集工具。在内部实现上,ServiceCore将调用this.logger.log(level, funcName, message)输出其内部运行日志。
通常,我们使用Corejs内置的日期输出器作为ServiceCore的日志收集工具:
const serviceCore = new Core.ServiceCore();
// 指定ServiceCore使用DateLogger进行日志收集
serviceCore.logger = new Core.DateLogger({ filePrefix: 'ServiceCore' });
2
3
提示
Corejs内置的日期输出器将同一日期周期内产生的日志归档至一个文件(组),且支持自动清理和文件分割。
ServiceCore内部运行日志的输出等级、方法名和文案存储在Core.Macros和Core.Messages中,我们可以通过提前修改这些宏变量的方式实现日志内容定制(比如:日志国际化)。
level:日志输出等级提示
日志输出等级存储在
Core.Macros中。宏名称 描述 默认值 SERVICE_CORE_INFOS_LOG_LEVEL信息日志等级 'infos'SERVICE_CORE_WARNS_LOG_LEVEL警告日志等级 'warns'SERVICE_CORE_ERROR_LOG_LEVEL错误日志等级 'error'funcName:调用方法名提示
调用方法名存储在
Core.Messages中。宏名称 默认值 SERVICE_CORE_FUNCNAME_LOG'服务核心'message:日志文案内容提示
日志文案内容存储在
Core.Messages中。定制日志文案内容时可以使用
${VAR_NAME}的形式引用内置变量名以获取特征信息。比如:我们可以使用
'当前状态下无法执行操作:[${funcName}]'引用内置变量名为'funcName'中的信息。宏名称 描述 内置变量名 输出等级 SERVICE_CORE_MESSAGE_INVALID_STATE当前状态下不允许执行操作 funcName:操作方法名SERVICE_CORE_WARNS_LOG_LEVELSERVICE_CORE_MESSAGE_INVALID_HANDLER待绑定的Handler类型无效 index:Handler位于绑定列表的索引SERVICE_CORE_WARNS_LOG_LEVELSERVICE_CORE_MESSAGE_INVALID_ROUTE_PATH待绑定的Handler请求路径无效 routePath:Handler的请求路径SERVICE_CORE_WARNS_LOG_LEVELSERVICE_CORE_MESSAGE_INVALID_PARAM_TYPE设置全局拦截器/错误拦截器/构建过程时参数无效 无 抛出异常,不产生日志 SERVICE_CORE_MESSAGE_SUCCESS_BIND_HANDLER成功绑定Handler routePath:Handler的请求路径SERVICE_CORE_INFOS_LOG_LEVELSERVICE_CORE_MESSAGE_SUCCESS_START_SERVERServiceCore启动成功 serverType:ServiceCore的服务类型;baseRoutePath:基础请求路径SERVICE_CORE_INFOS_LOG_LEVELSERVICE_CORE_MESSAGE_FAILURE_START_SERVERServiceCore启动失败 error:启动失败的原因SERVICE_CORE_ERROR_LOG_LEVEL