123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
- import * as express from 'express'
- import * as Api from './api'
- import * as Data from './query/data-model'
- import * as Coords from './algebra/coordinate'
- import Docs from './documentation'
- import ServerConfig from '../server-config'
- import { ConsoleLogger } from 'mol-util/console-logger'
- import { State } from './state'
- export default function init(app: express.Express) {
- function makePath(p: string) {
- return ServerConfig.apiPrefix + '/' + p;
- }
- // Header
- app.get(makePath(':source/:id/?$'), (req, res) => getHeader(req, res));
- // Box /:src/:id/box/:a1,:a2,:a3/:b1,:b2,:b3?text=0|1&space=cartesian|fractional
- app.get(makePath(':source/:id/box/:a1,:a2,:a3/:b1,:b2,:b3/?'), (req, res) => queryBox(req, res, getQueryParams(req, false)));
- // Cell /:src/:id/cell/?text=0|1&space=cartesian|fractional
- app.get(makePath(':source/:id/cell/?'), (req, res) => queryBox(req, res, getQueryParams(req, true)));
- app.get('*', (req, res) => {
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
- res.end(Docs);
- });
- }
- function mapFile(type: string, id: string) {
- return ServerConfig.mapFile(type || '', id || '');
- }
- function wrapResponse(fn: string, res: express.Response) {
- const w = {
- do404(this: any) {
- if (!this.headerWritten) {
- res.writeHead(404);
- this.headerWritten = true;
- }
- this.end();
- },
- writeHeader(this: any, binary: boolean) {
- if (this.headerWritten) return;
- 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="${fn}"`
- });
- this.headerWritten = true;
- },
- writeBinary(this: any, data: Uint8Array) {
- if (!this.headerWritten) this.writeHeader(true);
- return res.write(Buffer.from(data.buffer));
- },
- writeString(this: any, data: string) {
- if (!this.headerWritten) this.writeHeader(false);
- return res.write(data);
- },
- end(this: any) {
- if (this.ended) return;
- res.end();
- this.ended = true;
- },
- ended: false,
- headerWritten: false
- };
- return w;
- }
- function getSourceInfo(req: express.Request) {
- return {
- filename: mapFile(req.params.source, req.params.id),
- id: `${req.params.source}/${req.params.id}`
- };
- }
- function validateSourndAndId(req: express.Request, res: express.Response) {
- if (!req.params.source || req.params.source.length > 32 || !req.params.id || req.params.source.id > 32) {
- res.writeHead(404);
- res.end();
- ConsoleLogger.error(`Query Box`, 'Invalid source and/or id');
- return true;
- }
- return false;
- }
- async function getHeader(req: express.Request, res: express.Response) {
- if (validateSourndAndId(req, res)) {
- return;
- }
- let headerWritten = false;
- try {
- const { filename, id } = getSourceInfo(req);
- const header = await Api.getHeaderJson(filename, id);
- if (!header) {
- res.writeHead(404);
- return;
- }
- res.writeHead(200, {
- 'Content-Type': 'application/json; charset=utf-8',
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Headers': 'X-Requested-With'
- });
- headerWritten = true;
- res.write(header);
- } catch (e) {
- ConsoleLogger.error(`Header ${req.params.source}/${req.params.id}`, e);
- if (!headerWritten) {
- res.writeHead(404);
- }
- } finally {
- res.end();
- }
- }
- function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams {
- const a = [+req.params.a1, +req.params.a2, +req.params.a3];
- const b = [+req.params.b1, +req.params.b2, +req.params.b3];
- const detail = Math.min(Math.max(0, (+req.query.detail) | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1)
- const isCartesian = (req.query.space || '').toLowerCase() !== 'fractional';
- const box: Data.QueryParamsBox = isCell
- ? { kind: 'Cell' }
- : (isCartesian
- ? { kind: 'Cartesian', a: Coords.cartesian(a[0], a[1], a[2]), b: Coords.cartesian(b[0], b[1], b[2]) }
- : { kind: 'Fractional', a: Coords.fractional(a[0], a[1], a[2]), b: Coords.fractional(b[0], b[1], b[2]) });
- const asBinary = (req.query.encoding || '').toLowerCase() !== 'cif';
- const sourceFilename = mapFile(req.params.source, req.params.id)!;
- return {
- sourceFilename,
- sourceId: `${req.params.source}/${req.params.id}`,
- asBinary,
- box,
- detail
- };
- }
- async function queryBox(req: express.Request, res: express.Response, params: Data.QueryParams) {
- if (validateSourndAndId(req, res)) {
- return;
- }
- const outputFilename = Api.getOutputFilename(req.params.source, req.params.id, params);
- const response = wrapResponse(outputFilename, res);
- try {
- if (!params.sourceFilename) {
- response.do404();
- return;
- }
- let ok = await Api.queryBox(params, () => response);
- if (!ok) {
- response.do404();
- return;
- }
- } catch (e) {
- ConsoleLogger.error(`Query Box ${JSON.stringify(req.params || {})} | ${JSON.stringify(req.query || {})}`, e);
- response.do404();
- } finally {
- response.end();
- queryDone();
- }
- }
- function queryDone() {
- if (State.shutdownOnZeroPending) {
- process.exit(0);
- }
- }
|