server/index.js (137 lines of code) (raw):
// Copyright (c) 2017-2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
const path = require('path');
const process = require('process');
const Koa = require('koa');
const koaBodyparser = require('koa-bodyparser');
const koaCompress = require('koa-compress');
const koaSend = require('koa-send');
const koaStatic = require('koa-static');
const koaWebpack = require('koa-webpack');
const webpack = require('webpack');
const jwt = require('jsonwebtoken');
const webpackConfig = require('../webpack.config');
const grpcClient = require('./middleware/grpc-client');
const tchannelClient = require('./middleware/tchannel-client');
const router = require('./router');
const {
PEERS_DEFAULT,
REQUEST_RETRY_FLAGS_DEFAULT,
REQUEST_RETRY_LIMIT_DEFAULT,
REQUEST_TIMEOUT_DEFAULT,
SERVICE_NAME_DEFAULT,
TRANSPORT_CLIENT_TYPE_DEFAULT,
} = require('./constants');
const staticRoot = path.join(__dirname, '../dist');
const app = new Koa();
const transportClients = {
tchannel: tchannelClient,
grpc: grpcClient,
};
app.webpackConfig = webpackConfig;
app.init = function({
logErrors,
peers = process.env.CADENCE_TCHANNEL_PEERS || PEERS_DEFAULT,
retryFlags = REQUEST_RETRY_FLAGS_DEFAULT,
retryLimit = process.env.CADENCE_TCHANNEL_RETRY_LIMIT ||
REQUEST_RETRY_LIMIT_DEFAULT,
serviceName = process.env.CADENCE_TCHANNEL_SERVICE || SERVICE_NAME_DEFAULT,
timeout = REQUEST_TIMEOUT_DEFAULT,
transportClientType = process.env.TRANSPORT_CLIENT_TYPE ||
TRANSPORT_CLIENT_TYPE_DEFAULT, // 'tchannel', 'grpc'
useWebpack = process.env.NODE_ENV !== 'production',
enableAuth = process.env.ENABLE_AUTH === 'true',
authType = process.env.AUTH_TYPE,
authAdminJwtPrivateKey = process.env.AUTH_ADMIN_JWT_PRIVATE_KEY,
} = {}) {
const requestConfig = {
retryFlags,
retryLimit,
serviceName,
timeout,
};
const transportClient = transportClients[transportClientType];
if (!transportClient) {
throw new Error(
`Unexpected transport client "${transportClientType}". Only support 'tchannel' or 'grpc'.`
);
}
let compiler;
if (useWebpack) {
compiler = webpack(app.webpackConfig);
}
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
});
app
.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (
logErrors !== false &&
(typeof err.statusCode !== 'number' || err.statusCode >= 500)
) {
console.error(err);
}
ctx.status = err.statusCode || err.status || 500;
ctx.body = { message: err.message };
}
})
.use(koaBodyparser())
.use(
koaCompress({
filter: contentType => !contentType.startsWith('text/event-stream'),
})
)
.use(async function(ctx, next) {
if (enableAuth && authType === 'ADMIN_JWT' && authAdminJwtPrivateKey) {
ctx.authTokenHeaders = ctx.authTokenHeaders || {};
const token = jwt.sign(
{ admin: true, ttl: 10 },
authAdminJwtPrivateKey,
{
algorithm: 'RS256',
expiresIn: '10s',
}
);
ctx.authTokenHeaders['cadence-authorization'] = token;
}
await next();
})
.use(transportClient({ peers, requestConfig }))
.use(
useWebpack
? koaWebpack({
compiler,
dev: { stats: { colors: true } },
hot: { port: process.env.TEST_RUN ? 8082 : 8081 },
})
: koaStatic(staticRoot)
)
.use(router.routes())
.use(router.allowedMethods())
.use(async function(ctx, next) {
if (
['HEAD', 'GET'].includes(ctx.method) &&
!ctx.path.startsWith('/api')
) {
try {
ctx.set('X-Content-Type-Options', 'nosniff');
ctx.set('X-Frame-Options', 'SAMEORIGIN');
ctx.set('X-XSS-Protection', '1; mode=block');
if (useWebpack) {
const filename = path.join(compiler.outputPath, 'index.html');
ctx.set('content-type', 'text/html');
ctx.body = compiler.outputFileSystem.readFileSync(filename);
} else {
await koaSend(ctx, 'index.html', { root: staticRoot });
}
} catch (err) {
if (err.status !== 404) {
throw err;
}
}
}
});
return app;
};
module.exports = app;