api-web.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as fs from 'fs';
  7. import * as path from 'path';
  8. import * as express from 'express';
  9. import Config from '../config';
  10. import { ConsoleLogger } from 'mol-util/console-logger';
  11. import { resolveJob } from './query';
  12. import { JobManager } from './jobs';
  13. import { UUID } from 'mol-util';
  14. import { LandingPage } from './landing';
  15. function makePath(p: string) {
  16. return Config.appPrefix + '/' + p;
  17. }
  18. function wrapResponse(fn: string, res: express.Response) {
  19. const w = {
  20. doError(this: any, code = 404, message = 'Not Found.') {
  21. if (!this.headerWritten) {
  22. res.status(code).send(message);
  23. this.headerWritten = true;
  24. }
  25. this.end();
  26. },
  27. writeHeader(this: any, binary: boolean) {
  28. if (this.headerWritten) return;
  29. res.writeHead(200, {
  30. 'Content-Type': binary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
  31. 'Access-Control-Allow-Origin': '*',
  32. 'Access-Control-Allow-Headers': 'X-Requested-With',
  33. 'Content-Disposition': `inline; filename="${fn}"`
  34. });
  35. this.headerWritten = true;
  36. },
  37. writeBinary(this: any, data: Uint8Array) {
  38. if (!this.headerWritten) this.writeHeader(true);
  39. return res.write(Buffer.from(data.buffer));
  40. },
  41. writeString(this: any, data: string) {
  42. if (!this.headerWritten) this.writeHeader(false);
  43. return res.write(data);
  44. },
  45. end(this: any) {
  46. if (this.ended) return;
  47. res.end();
  48. this.ended = true;
  49. },
  50. ended: false,
  51. headerWritten: false
  52. };
  53. return w;
  54. }
  55. const responseMap = new Map<UUID, express.Response>();
  56. async function processNextJob() {
  57. if (!JobManager.hasNext()) return;
  58. const job = JobManager.getNext();
  59. const response = responseMap.get(job.id)!;
  60. responseMap.delete(job.id);
  61. const filenameBase = `${job.entryId}_${job.queryDefinition.name.replace(/\s/g, '_')}`
  62. const writer = wrapResponse(job.responseFormat.isBinary ? `${filenameBase}.bcif` : `${filenameBase}.cif`, response);
  63. try {
  64. const encoder = await resolveJob(job);
  65. writer.writeHeader(job.responseFormat.isBinary);
  66. encoder.writeTo(writer);
  67. } catch (e) {
  68. ConsoleLogger.errorId(job.id, '' + e);
  69. writer.doError(404, '' + e);
  70. } finally {
  71. writer.end();
  72. ConsoleLogger.logId(job.id, 'Query', 'Finished.');
  73. setImmediate(processNextJob);
  74. }
  75. }
  76. // function mapQuery(app: express.Express, queryName: string, queryDefinition: QueryDefinition) {
  77. // app.get(makePath(':entryId/' + queryName), (req, res) => {
  78. // ConsoleLogger.log('Server', `Query '${req.params.entryId}/${queryName}'...`);
  79. // if (JobManager.size >= Config.maxQueueLength) {
  80. // res.status(503).send('Too many queries, please try again later.');
  81. // res.end();
  82. // return;
  83. // }
  84. // const jobId = JobManager.add('pdb', req.params.entryId, queryName, req.query);
  85. // responseMap.set(jobId, res);
  86. // if (JobManager.size === 1) processNextJob();
  87. // });
  88. // }
  89. export function initWebApi(app: express.Express) {
  90. app.get(makePath('static/:format/:id'), async (req, res) => {
  91. const binary = req.params.format === 'bcif';
  92. const id = req.params.id;
  93. const fn = Config.mapFile(binary ? 'pdb-bcif' : 'pdb-cif', id);
  94. if (!fn || !fs.existsSync(fn)) {
  95. res.status(404);
  96. res.end();
  97. return;
  98. }
  99. fs.readFile(fn, (err, data) => {
  100. if (err) {
  101. res.status(404);
  102. res.end();
  103. return;
  104. }
  105. const f = path.parse(fn);
  106. res.writeHead(200, {
  107. 'Content-Type': binary ? 'application/octet-stream' : 'text/plain; charset=utf-8',
  108. 'Access-Control-Allow-Origin': '*',
  109. 'Access-Control-Allow-Headers': 'X-Requested-With',
  110. 'Content-Disposition': `inline; filename="${f.name}${f.ext}"`
  111. });
  112. res.write(data);
  113. res.end();
  114. });
  115. })
  116. app.get(makePath('api/v1'), (req, res) => {
  117. const query = /\?(.*)$/.exec(req.url)![1];
  118. const args = JSON.parse(decodeURIComponent(query));
  119. const name = args.name;
  120. const entryId = args.id;
  121. const queryParams = args.params || { };
  122. const jobId = JobManager.add({
  123. sourceId: 'pdb',
  124. entryId,
  125. queryName: name,
  126. queryParams,
  127. options: { modelNums: args.modelNums, binary: args.binary }
  128. });
  129. responseMap.set(jobId, res);
  130. if (JobManager.size === 1) processNextJob();
  131. });
  132. app.get('*', (req, res) => {
  133. res.send(LandingPage);
  134. });
  135. // for (const q of QueryList) {
  136. // mapQuery(app, q.name, q.definition);
  137. // }
  138. }