本文目录
本系列文章
SAP CAP篇一: 快速创建一个Service,基于Java的实现
SAP CAP篇二:为Service加上数据库支持
SAP CAP篇三:定义Model
SAP CAP篇四:为CAP添加Fiori Elements程序(1)
SAP CAP篇五:为CAP添加Fiori Elements程序(2)
SAP CAP篇六:为CAP添加Fiori Elements程序(3)
SAP CAP篇七:为CAP添加Fiori Launchpad入口 (Sandbox环境)
SAP CAP篇八:为CAP添加App Router并支持Fiori Launchpad (Sandbox环境)
SAP CAP篇九:升级为SAP CDS 7.0, CAP Java 2以及Spring Boot 3
SAP CAP篇十:理解Fiori UI的Annoation定义
SAP CAP篇十一:支持Media Object:图片、附件等
SAP CAP篇十二:AppRouter 深入研究
理解现有程序
本篇基于上一篇 SAP CAP篇十一:支持Media Object:图片、附件等 代码基础。
app
文件夹中的package.json
打开app
文件夹中的package.json
,可以发现start
命令定义如下:
json
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
理解approuter.js
看看上述start
命令的approuter.js
,其中,关键的部分是文件末尾的启动AppRouter部分。
javascript
if (require.main === module) {
let ar = new Approuter();
ar.start();
}
修改现有程序
理解了现有程序之后,可以对现有程序进行修改。
修改package.json
本步骤的修改是在app
文件夹下。
修改package.json
文件中的'scripts'部分:
json
"scripts": {
"start": "node index.js"
}
新建index.js
由上述script所示,这里的start
命令将执行index.js
。
该新建的index.js
文件如下:
javascript
const approuter = require('@sap/approuter');
const ar = approuter();
ar.start();
其实,上段代码其实就是Approuter.js的核心启动代码:
这时候,启动Service跟Approuter,所有功能都正常运行。换言之,上述代码修改是功能无损的修改,但是它提供了额外的可能。
在Approuter中显示额外的逻辑
如果仔细研读approuter.js
的源码,它其实是node.js的程序。而其中的start
方法是其中的关键:
javascript
Approuter.prototype.start = function (options, callback) {
let self = this;
if (dynamicRoutingUtils.isDynamicRouting()) {
self.first.use(dynamicRoutingUtils.initialize(self));
if (options) {
delete options.port;
options.getRouterConfig = dynamicRoutingUtils.getRouterConfig;
} else {
options = {'getRouterConfig': dynamicRoutingUtils.getRouterConfig};
}
}
if (options) {
validators.validateApprouterStartOptions(options);
options = _.cloneDeep(options);
} else {
options = {};
}
callback = optionalCallback(callback);
if (this.cmdParser) {
this.cmdParser.parse(process.argv);
options = _.defaults(options, this.cmdParser);
}
addImplicitExtension(this, options);
let logger = loggerUtil.getLogger('/approuter');
logger.info('Application router version %s', require('./package.json').version);
let app = bootstrap(options);
app.logger = logger;
app.approuter = this;
this._app = app;
loggerUtil.getAuditLogger(function(err, auditLogger){
if (err) {
throw err;
}
app.auditLogger = auditLogger;
serverLib.start(app, function (err, server) {
self._server = server;
callback(err);
});
});
};
继续研读下去,其中最关键的serverLib.start(...)
的逻辑实现在一个server.js
的文件中,而server的start方法:
javascript
exports.start = function (app, callback) {
let routerConfig = app.get('mainRouterConfig');
let server;
if (routerConfig.http2Support){
server = http2.createServer(app);
} else if (routerConfig.httpsOptions) {
server = https.createServer(routerConfig.httpsOptions, app);
} else {
server = http.createServer(app);
}
if (routerConfig.incomingConnectionTimeout !== undefined) {
server.timeout = routerConfig.incomingConnectionTimeout;
}
server.keepAliveTimeout = routerConfig.serverKeepAlive || 0;
let wsServer = new WsProxy(app);
wsServer.listen(server);
server.on('error', callback);
server.listen(routerConfig.serverPort, function () {
app.logger.info('Application router is listening on port: ' +
server.address().port);
callback(undefined, new Server(server, wsServer));
});
};
而引用到的lib在文件头部有定义:
javascript
const http = require('http');
const https = require('https');
const http2 = require('http2');
const WsProxy = require('./websockets/WsProxy');
const util = require('util');
至此,AppRouter的逻辑很清楚了,就是一个基于nodejs的server。所以,通过其可以实现一定额外的逻辑,譬如proxy之类。
添加一些额外的Logger
虽然可以通过Approuter实现类似proxy的逻辑,但是这不是本篇的重点。接下来,通过AppRouter来实现额外的Log写入逻辑,作为测试。
javascript
const approuter = require('@sap/approuter');
const { Console } = require("console");
// get fs module for creating write streams
const fs = require("fs");
ar.beforeRequestHandler.use('/api', async (req, res, next) => {
const myLogger = new Console({
stdout: fs.createWriteStream("normalStdout.txt"),
stderr: fs.createWriteStream("errStdErr.txt"),
});
const { rawHeaders, method, originalUrl, url } = req;
let body = [];
let errorMessage = null;
// saving to normalStdout.txt file
myLogger.log(`originalUrl = ${originalUrl}`);
myLogger.log(`method = ${method}`);
myLogger.log(JSON.stringify(rawHeaders));
// if (method === 'POST' || method === 'PUT') {
// myLogger.log(`========================================`);
// myLogger.log(req);
// for await (const chunk of req) {
// myLogger.log(`+++ 111 ++++++++++++++++++++++++++++++`);
// myLogger.log(`+++ ${Date.now()} for each data`);
// myLogger.log(`Log Callback of 'on': ${chunk}`);
// body.push(chunk);
// }
// myLogger.log(`+++ 222 ++++++++++++++++++++++++++++++`);
// myLogger.log(`+++ ${Date.now()} of all`);
// body = Buffer.concat(body).toString();
// myLogger.log(`Log Callback of 'end': ${body}`);
// myLogger.log(`========================================`);
// myLogger.log(req);
// }
myLogger.log(`+++ 000 ++++++++++++++++++++++++++++++`);
myLogger.log(`+++ ${Date.now()} before next()`);
next();
});
运行后,所有的对/api
的情节都会被写入额外的normalStdout.txt
。
其中,被注释的代码部分会打印完整request
的body
部分,但是作为副作用,request这个stream已经被close了,next()
再发送到后续的处理就会失败,所以这里的代码仅作为参考用途。
对应代码及branch
与本文配套的代码参见这里。
本篇对应的branch是8_approuter
。