molstar
Version:
A comprehensive macromolecular library.
178 lines (177 loc) • 7.53 kB
JavaScript
"use strict";
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createResultWriter = createResultWriter;
exports.initWebApi = initWebApi;
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs"));
const path = tslib_1.__importStar(require("path"));
const express = tslib_1.__importStar(require("express"));
const config_1 = require("../config");
const console_logger_1 = require("../../../mol-util/console-logger");
const query_1 = require("./query");
const jobs_1 = require("./jobs");
const api_1 = require("./api");
const api_schema_1 = require("./api-schema");
const swagger_ui_1 = require("../../common/swagger-ui");
const api_web_multiple_1 = require("./api-web-multiple");
const writer_1 = require("../utils/writer");
const string_1 = require("../../../mol-util/string");
const util_1 = require("../../common/util");
function makePath(p) {
return config_1.ModelServerConfig.apiPrefix + '/' + p;
}
const responseMap = new Map();
async function processNextJob() {
if (!jobs_1.JobManager.hasNext())
return;
const job = jobs_1.JobManager.getNext();
responseMap.delete(job.id);
const writer = job.writer;
try {
await (0, query_1.resolveJob)(job);
}
catch (e) {
console_logger_1.ConsoleLogger.errorId(job.id, '' + e);
writer.doError(404, '' + e);
}
finally {
writer.end();
console_logger_1.ConsoleLogger.logId(job.id, 'Query', 'Finished.');
setImmediate(processNextJob);
}
}
function createResultWriter(response, params) {
const filenameBase = params.entryId && params.queryName
? `${params.entryId}_${(0, string_1.splitCamelCase)(params.queryName.replace(/\s/g, '_'), '-').toLowerCase()}`
: `result`;
return new writer_1.SimpleResponseResultWriter(params.filename || `${filenameBase}.${params.encoding}`, response, params.encoding === 'bcif', params.download);
}
function mapQuery(app, queryName, queryDefinition) {
function createJob(queryParams, req, res) {
const entryId = req.params.id;
const commonParams = (0, api_1.normalizeRestCommonParams)(req.query);
const resultWriterParams = { encoding: commonParams.encoding, download: !!commonParams.download, filename: commonParams.filename, entryId, queryName };
const jobId = jobs_1.JobManager.add({
entries: [(0, jobs_1.JobEntry)({
sourceId: commonParams.data_source || config_1.ModelServerConfig.defaultSource,
entryId,
queryName: queryName,
queryParams,
modelNums: commonParams.model_nums,
copyAllCategories: !!commonParams.copy_all_categories,
transform: commonParams.transform
})],
writer: createResultWriter(res, resultWriterParams),
options: { binary: commonParams.encoding === 'bcif', encoding: commonParams.encoding }
});
responseMap.set(jobId, res);
if (jobs_1.JobManager.size === 1)
processNextJob();
}
app.get(makePath('v1/:id/' + queryName), (req, res) => {
const queryParams = (0, api_1.normalizeRestQueryParams)(queryDefinition, req.query);
createJob(queryParams, req, res);
});
app.post(makePath('v1/:id/' + queryName), (req, res) => {
const queryParams = req.body;
createJob(queryParams, req, res);
});
}
function serveStatic(req, res) {
const source = req.params.source === 'bcif'
? 'pdb-bcif'
: req.params.source === 'cif'
? 'pdb-cif'
: req.params.source;
const id = req.params.id;
const [fn, format] = (0, config_1.mapSourceAndIdToFilename)(source, id);
const binary = format === 'bcif' || fn.indexOf('.bcif') > 0;
if (!fn || !fs.existsSync(fn)) {
res.status(404);
res.end();
return;
}
fs.readFile(fn, (err, data) => {
if (err) {
res.status(404);
res.end();
return;
}
const f = path.parse(fn);
res.writeHead(200, {
'Content-Type': binary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Requested-With',
'Content-Disposition': `inline; filename="${f.name}${f.ext}"`
});
res.write(data);
res.end();
});
}
function createMultiJob(spec, res) {
var _a;
const writer = spec.asTarGz
? new writer_1.TarballResponseResultWriter((0, api_web_multiple_1.getMultiQuerySpecFilename)(), res)
: createResultWriter(res, { encoding: spec.encoding, download: !!spec.download, filename: spec.filename });
if (spec.queries.length > config_1.ModelServerConfig.maxQueryManyQueries) {
writer.doError(400, `query-many queries limit (${config_1.ModelServerConfig.maxQueryManyQueries}) exceeded.`);
return;
}
const jobId = jobs_1.JobManager.add({
entries: spec.queries.map(q => (0, jobs_1.JobEntry)({
sourceId: q.data_source || config_1.ModelServerConfig.defaultSource,
entryId: q.entryId,
queryName: q.query,
queryParams: q.params || {},
modelNums: q.model_nums,
copyAllCategories: !!q.copy_all_categories
})),
writer,
options: { binary: ((_a = spec.encoding) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'bcif', tarball: spec.asTarGz }
});
responseMap.set(jobId, res);
if (jobs_1.JobManager.size === 1)
processNextJob();
}
function initWebApi(app) {
app.use(express.json({ limit: '1mb' }));
app.get(makePath('static/:source/:id'), (req, res) => serveStatic(req, res));
app.get(makePath('v1/static/:source/:id'), (req, res) => serveStatic(req, res));
app.get(makePath('v1/query-many'), (req, res) => {
const query = /\?query=(.*)$/.exec(req.url)[1];
const params = JSON.parse(decodeURIComponent(query));
createMultiJob(params, res);
});
app.post(makePath('v1/query-many'), (req, res) => {
const params = req.body;
req.setTimeout(config_1.ModelServerConfig.requestTimeoutMs);
createMultiJob(params, res);
});
app.use(express.json({ limit: '20mb' }));
for (const q of api_1.QueryList) {
mapQuery(app, q.name, q.definition);
}
// Reports server health depending on `healthCheckPath` config prop
app.get(makePath('health-check'), (_, res) => (0, util_1.healthCheck)(res, config_1.ModelServerConfig.healthCheckPath));
const schema = (0, api_schema_1.getApiSchema)();
app.get(makePath('openapi.json'), (req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Requested-With'
});
res.end(JSON.stringify(schema));
});
app.use(makePath(''), (0, swagger_ui_1.swaggerUiAssetsHandler)());
app.get(makePath(''), (0, swagger_ui_1.swaggerUiIndexHandler)({
openapiJsonUrl: makePath('openapi.json'),
apiPrefix: config_1.ModelServerConfig.apiPrefix,
title: 'ModelServer API',
shortcutIconLink: api_schema_1.shortcutIconLink
}));
}