SAP CAP篇十二:AppRouter 深入研究

本文目录

本系列文章

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

其中,被注释的代码部分会打印完整requestbody部分,但是作为副作用,request这个stream已经被close了,next()再发送到后续的处理就会失败,所以这里的代码仅作为参考用途。

对应代码及branch

与本文配套的代码参见这里

本篇对应的branch是8_approuter

相关推荐
carfied-feifei9 天前
基于 Nginx 的 CDN 基础实现
nginx·cloud native
dami_king9 天前
云原生后端|实践?
后端·阿里云·云原生·cloud native·csdn开发云·
昵称难产中1 个月前
浅谈云计算16 | 存储虚拟化技术
linux·计算机网络·云原生·云计算·cloud native
predisw2 个月前
请求是如何通过k8s service 路由到对应的pod
云原生·容器·kubernetes·cloud native
MavenTalk2 个月前
说说聊聊CNCF(云原生计算基金会)
微服务·云原生·架构·kubernetes·cloud native·cncf
MavenTalk3 个月前
Cloud Native 云原生后端的开发注意事项
后端·docker·云原生·容器化·cloud native·ai原生
Elastic 中国社区官方博客6 个月前
停止项目大小调整,开始搜索层自动缩放!
大数据·运维·人工智能·elasticsearch·搜索引擎·kubernetes·cloud native
字节数据平台6 个月前
数据飞轮驱动消费行业变革,火山引擎数智平台助力门店数智化转型
大数据·cloud native
Akamai中国8 个月前
分布式+可移植,上云后降本增效的关键
分布式·云原生·云计算·cloud native·akamai·linode
MinIO官方账号1 年前
在MinIO中添加Pools(池)并扩展容量
华为云·云计算·github·硬件架构·minio·cloud native