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;