第一步:生成server主入口
- 这边startserver主函数,.prepare是nextjs提供的后端渲染套件,返回一个promise对象,then中的逻辑不多说,主要是加载各类中间层,核心内容是server.use(kraken(paypalize(options)))
const config = require('./config')
const options = {
onconfig: function (config, next) {
next(null, config)
}
}
function startServer(server) {
return app
.prepare()
.then(async () => {
// Hide server info for security.
server.disable('x-powered-by')
server.set('trust proxy', true)
server.use(cookieParser())
server.use(rewrite(`${getAppBaseUrl()}/*`, '/$1'))
server.use(kraken(paypalize(options)))
server.use(express.static(path.join(__dirname, '../public')))
await nextI18next.initPromise
const port = process.env.PORT
return server.listen(port, (err) => {
if (err) throw err
// eslint-disable-next-line
console.log(`>>> Ready on http://localhost:${port}`)
})
})
.catch((error) => {
logger.error('Exit with 500', error)
process.exit(1)
})
}
第二步:建立kraken-paypalize中间件
- 原本krakenjs可以接受onchange这个钩子函数,如下所示 , onchange可以挨个读取config.js(默认的话)中的配置进行迭代,如果当前配置符合业务需求则调用next(null, config)开始下一项配置
var kraken = require('kraken-js'),
app = require('express')(),
options = {
onconfig: function (config, next) {
config.get('view engines:js:renderer:arguments').push(app);
next(null, config);
}
/* more options are documented in the README */
},
port = process.env.PORT || 8000;
app.use(kraken(options));
- 这边paypal重写了这边的config参数,加入一些业务中的配置所以采用了paypalize(options)
第三步:paypalize中间件主体 (index.js)
- 定义全局捕获node错误的两个函数,这里不做赘述,主要是为了规范错误输出以及输出log
process.on('unhandledRejection', UncaughtHandlers.rejection);
process.on('uncaughtException', UncaughtHandlers.uncaught);
- 初始化一些参数变量,例如basedir,protocols等,其实这些配置都是和kraken原来的配置参数同名的,方便之后覆盖
const { basedir = Path.dirname(Caller()), protocols = {}, startupHeaders = {} } = defaults;
const { vault = {}, listenBeforeBootstrap = false } = Shush(Path.join(basedir, 'config/config.json')); // Shush will throw on missing file.
const appname = AppName(basedir);
if (process.env.DEPLOY_ENV) {
vault.name = appname;
vault.env = Environment._env();
}
- 准备覆盖kraken原有的option,这边的核心内容是为protocols新增两个解析器vault()以及keymaker(),其他其实要么{}要么写死的
const overrides = {
vault,
basedir,
listenBeforeBootstrap,
protocols: Object.assign({}, protocols, {
vault: VaultHandler(vault)
}, {
keymaker: KeymakerHandler({ appname }) //appname is needed for DEV/QA
}),
startupHeaders: Object.assign({
'X-SLR-RETRY': 'Server is starting.'
}, startupHeaders)
};
const options = Object.assign({}, defaults, overrides);
- 覆盖kraken option中的ttl部分,不做赘述,paypal这边注释写了为啥
//If `maxTTL` is not zero, Use ReDNS to cache dns lookups with a TTL
//Set a 1 minute max TTL value by default.
if (options.maxTTL !== 0 && options.dnsCache) {
const redns = new ReDNS({
maxTTL: options.maxTTL || 60
});
Dns.lookup = redns.lookup.bind(redns);
}
- 覆盖kraken option中的onKrakenMount参数,其实主要是为了修改app实例的listen方法来改变监听行为,Server这个对象是paypal事先定义好的,这边这个函数也就起到了切换的作用
const onKrakenMountFunc = options.onKrakenMount;
options.onKrakenMount = function onKrakenMount (app, _options) {
// execute onkrakenmount function if any.
onKrakenMountFunc && onKrakenMountFunc(app, _options);
if(!_options.listenBeforeBootstrap) {
overrideListen(app);
}
}
const overrideListen = (app) => {
app.listen = function listen() {
const server = new Server(app);
return server.listen.apply(server, arguments);
};
};
- 最后一步去复写onconfig函数,由于原先kraken中onconfig函数是一个迭代器,所以这边也是一样的,复写函数一样需要返回一个带next的函数迭代器
options.onconfig = WrapOnConfig(Immutable.Map(options));
第四步:通过WrapOnConfig去加载业务上所需的一些中间层
- 这边的核心代码其实没多少,概括下就是三步走,第一将onconfig包装成promise对象,第二stepbystep去循环包裹相应的中间层,第三分别记录包裹开始时间和结束时间输出日志
const original = options.get('onconfig') && promisify(options.get('onconfig'));
const steps = Steps(options);
...
try {
for (const step of steps) {
const stepStart = process.hrtime();
nconfig = await step(nconfig);
if (step.name) {
benchmark[step.name] = toMilli(process.hrtime(stepStart));
}
}
if (original) {
const onconfigStart = process.hrtime();
nconfig = await original(nconfig);
benchmark['onconfig'] = toMilli(process.hrtime(onconfigStart));
}
next(null, nconfig);
}
第五步:开始加载中间层(以default为例子)
- 这边注意,所有的中间层的输入输出一定是 参数config=》async function(){return config}的形式,default中间层涉及的业务逻辑并不多,主要功能是将appConfig(kraken也就是项目中配置的config)和paypalizeConfig(paypalize中间层中的config)合并在一起(merge),以及将appProtocols(本来kraken自带的一些解析器)和paypalizeProtocols(主要是vault和keymaker这两个的解析器)合并在一起,合并用的npmpackage为confit
const basedir = options.get('basedir');
const configdir = options.get('configdir') || Path.join(__dirname, '../..');
const protocolOverrides = options.get('protocols');
const appbasedir = Path.resolve(basedir);
const appProtocols = Protocols(appbasedir, protocolOverrides);
const paypalizebasedir = Path.resolve(configdir);
const paypalizeProtocols = Protocols(paypalizebasedir, protocolOverrides);
.....