@universis/jrs-worker
Version:
JasperReports Server Worker for Universis Platform
128 lines (121 loc) • 4.6 kB
text/typescript
import express from 'express';
import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware';
import genericPool from 'generic-pool';
import { ConfigurationBase, Args, TraceUtils } from '@themost/common';
import { URL } from 'url';
class JrsWorker {
private active: boolean = false;
// eslint-disable-next-line @typescript-eslint/require-await
async connect(): Promise<this> {
this.active = true;
return this;
}
// eslint-disable-next-line @typescript-eslint/require-await
async disconnect() {
if (this.active) {
this.active = false;
}
}
}
const factory = {
create: (): Promise<JrsWorker> => {
return new JrsWorker().connect();
},
destroy: (worker: JrsWorker): Promise<void> => {
return worker.disconnect();
}
};
declare global {
namespace Express {
interface Request {
worker: JrsWorker
}
}
}
declare module 'http' {
interface IncomingMessage {
worker: JrsWorker
}
}
function jrsWorkerRouter(configuration: ConfigurationBase) {
// prepare router
const router = express.Router();
// create worker pool (limit to 1 worker)
/**
* get jasper server configuration
*/
const jasperServer: { target: string, workerPool: any } = configuration.getSourceAt('settings/jrsWorker');
// get worker pool options
// read more about generic pool options at https://github.com/coopernurse/node-pool?tab=readme-ov-file#createpool
const opts = Object.assign({}, {
"max": 1, // maximum size of the pool (the default is 1)
"min": 0, // minimum size of the pool
}, jasperServer.workerPool);
// create worker pool
const pool = genericPool.createPool<JrsWorker>(factory, opts);
// check if jasper server configuration is valid
Args.check(jasperServer && jasperServer.target, 'Jasper Server configuration is not valid. Expected target server.');
// use jasper server target as proxy target
const { pathname } = new URL(jasperServer.target);
const target = new URL('/', jasperServer.target).toString() ;
router.use(pathname, (req, res, next) => {
void pool.acquire().then((worker) => {
req.worker = worker;
next();
}).catch((err) => {
return next(err);
});
}, createProxyMiddleware({
target,
logLevel: configuration.getSourceAt('settings/jrsWorker/logLevel') || 'info',
changeOrigin: true,
selfHandleResponse: true,
onError: (err, req, res) => {
// get request as url object
const requestURL = new URL(req.url, jasperServer.target);
// remove client access token (for security reasons)
requestURL.searchParams.delete('REPORT_CLIENT_TOKEN');
// log error
TraceUtils.error(`Proxy error on ${requestURL.toString()}`);
TraceUtils.error(err);
// release worker
// eslint-disable-next-line @typescript-eslint/no-floating-promises
pool.release(req.worker).then(() => {
// and send error to client
res.writeHead(500);
res.end('An error has occurred while proxying request to report server.');
}).catch((error: Error) => {
// log error unhandled error while finalizing request
TraceUtils.error(error);
// write error
res.writeHead(500);
res.end('An error has occurred while finalizing request.');
}).finally(() => {
// remove worker from request
delete req.worker;
});
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onClose: (res, socket, head) => {
const worker = res.req && res.req.worker;
if (worker == null) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
pool.release(res.req.worker).then(() => {
delete res.req.worker;
});
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/no-unused-vars
onProxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
await pool.release(req.worker);
// remove worker from request
delete req.worker;
return responseBuffer;
})
}));
return router;
}
export {
jrsWorkerRouter
}