SAP CAP篇十二:AppRouter 深入研究

本文目录

  • 本系列文章
  • 理解现有程序
    • `app`文件夹中的`package.json`
    • 理解`approuter.js`
  • 修改现有程序
    • 修改`package.json`
    • 新建`index.js`
    • 在Approuter中显示额外的逻辑
  • 添加一些额外的Logger
  • 对应代码及branch

本系列文章

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命令定义如下:

"scripts": {
	"start": "node node_modules/@sap/approuter/approuter.js"
}

理解approuter.js

看看上述start命令的approuter.js,其中,关键的部分是文件末尾的启动AppRouter部分。

if (require.main === module) {
  let ar = new Approuter();
  ar.start();
}

修改现有程序

理解了现有程序之后,可以对现有程序进行修改。

修改package.json

本步骤的修改是在app文件夹下。

修改package.json文件中的’scripts’部分:

"scripts": {
	"start": "node index.js"
}

新建index.js

由上述script所示,这里的start命令将执行index.js

该新建的index.js文件如下:

const approuter = require('@sap/approuter');
const ar = approuter();

ar.start();

其实,上段代码其实就是Approuter.js的核心启动代码:

这时候,启动Service跟Approuter,所有功能都正常运行。换言之,上述代码修改是功能无损的修改,但是它提供了额外的可能。

在Approuter中显示额外的逻辑

如果仔细研读approuter.js的源码,它其实是node.js的程序。而其中的start方法是其中的关键:

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方法:

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在文件头部有定义:

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写入逻辑,作为测试。

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

你可能感兴趣的:(Web,Programming,ABAP/SAP,Spring,Boot,SAP,CAP,SAP,CDS,Fiori,Elements,Fiori,Launchapd,Cloud,Native)