Add cache control headers to server
This commit is contained in:
parent
f3488f631e
commit
a1caf2b16a
|
@ -0,0 +1,81 @@
|
|||
const {HttpError} = require('../server/HttpError');
|
||||
const {RequestHandler} = require('../server/RequestHandler');
|
||||
const {VirtualSequenceDiagram} = require('../../lib/sequence-diagram');
|
||||
|
||||
const NUM_MATCH = '[0-9]+(?:\\.[0-9]+)?';
|
||||
|
||||
function beginTimer() {
|
||||
return process.hrtime();
|
||||
}
|
||||
|
||||
function endTimer(timer) {
|
||||
const delay = process.hrtime(timer);
|
||||
return delay[0] * 1e9 + delay[1];
|
||||
}
|
||||
|
||||
function getNumeric(v, name) {
|
||||
if(!v) {
|
||||
return null;
|
||||
}
|
||||
const n = Number.parseFloat(v);
|
||||
if(Number.isNaN(n)) {
|
||||
throw new HttpError(400, 'Invalid value for ' + name);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function readEncoded(str, encoding) {
|
||||
switch(encoding) {
|
||||
case 'b64':
|
||||
return Buffer
|
||||
.from(decodeURIComponent(str), 'base64')
|
||||
.toString('utf8');
|
||||
case 'uri':
|
||||
return str.split('/').map(decodeURIComponent).join('\n');
|
||||
default:
|
||||
throw new HttpError(400, 'Unknown encoding');
|
||||
}
|
||||
}
|
||||
|
||||
class RenderRequestHandler extends RequestHandler {
|
||||
constructor(baseUrlPattern) {
|
||||
super('GET', new RegExp(
|
||||
`^${baseUrlPattern}/` +
|
||||
`(?:(?:w(${NUM_MATCH}))?(?:h(${NUM_MATCH}))?/|z(${NUM_MATCH})/)?` +
|
||||
'(?:(uri|b64)/)?' +
|
||||
'(.*?)' +
|
||||
'(?:\\.(svg))?$',
|
||||
'i'
|
||||
));
|
||||
this.info = `Rendering sequence diagrams at ${baseUrlPattern}/`;
|
||||
}
|
||||
|
||||
handle(req, res, {match, pickEncoding, log, writeEncoded}) {
|
||||
this.applyCommonHeaders(req, res);
|
||||
|
||||
const encoding = pickEncoding();
|
||||
const size = {
|
||||
height: getNumeric(match[2], 'height'),
|
||||
width: getNumeric(match[1], 'width'),
|
||||
zoom: getNumeric(match[3], 'zoom'),
|
||||
};
|
||||
const code = readEncoded(match[5], (match[4] || 'uri').toLowerCase());
|
||||
const format = (match[6] || 'svg').toLowerCase();
|
||||
|
||||
const timer = beginTimer();
|
||||
const svg = VirtualSequenceDiagram.render(code, {size});
|
||||
const delay = endTimer(timer);
|
||||
log('RENDER (' + (delay / 1e6).toFixed(3) + 'ms)');
|
||||
|
||||
switch(format) {
|
||||
case 'svg':
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
writeEncoded(encoding, svg);
|
||||
break;
|
||||
default:
|
||||
throw new HttpError(400, 'Unsupported image format');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {RenderRequestHandler};
|
|
@ -1,78 +0,0 @@
|
|||
const {HttpError} = require('../server/HttpError');
|
||||
const {RequestHandler} = require('../server/RequestHandler');
|
||||
const {VirtualSequenceDiagram} = require('../../lib/sequence-diagram');
|
||||
|
||||
function beginTimer() {
|
||||
return process.hrtime();
|
||||
}
|
||||
|
||||
function endTimer(timer) {
|
||||
const delay = process.hrtime(timer);
|
||||
return delay[0] * 1e9 + delay[1];
|
||||
}
|
||||
|
||||
const NUM_MATCH = '[0-9]+(?:\\.[0-9]+)?';
|
||||
const MATCH_RENDER = new RegExp(
|
||||
'^/render/' +
|
||||
`(?:(?:w(${NUM_MATCH}))?(?:h(${NUM_MATCH}))?/|z(${NUM_MATCH})/)?` +
|
||||
'(?:(uri|b64)/)?' +
|
||||
'(.*?)' +
|
||||
'(?:\\.(svg))?$',
|
||||
'i'
|
||||
);
|
||||
|
||||
function getNumeric(v, name) {
|
||||
if(!v) {
|
||||
return null;
|
||||
}
|
||||
const n = Number.parseFloat(v);
|
||||
if(Number.isNaN(n)) {
|
||||
throw new HttpError(400, 'Invalid value for ' + name);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function readEncoded(str, encoding) {
|
||||
switch(encoding) {
|
||||
case 'b64':
|
||||
return Buffer
|
||||
.from(decodeURIComponent(str), 'base64')
|
||||
.toString('utf8');
|
||||
case 'uri':
|
||||
return str.split('/').map(decodeURIComponent).join('\n');
|
||||
default:
|
||||
throw new HttpError(400, 'Unknown encoding');
|
||||
}
|
||||
}
|
||||
|
||||
function handleRender(req, res, {match, pickEncoding, log, writeEncoded}) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
const encoding = pickEncoding();
|
||||
const size = {
|
||||
height: getNumeric(match[2], 'height'),
|
||||
width: getNumeric(match[1], 'width'),
|
||||
zoom: getNumeric(match[3], 'zoom'),
|
||||
};
|
||||
const code = readEncoded(match[5], (match[4] || 'uri').toLowerCase());
|
||||
const format = (match[6] || 'svg').toLowerCase();
|
||||
|
||||
const timer = beginTimer();
|
||||
const svg = VirtualSequenceDiagram.render(code, {size});
|
||||
const delay = endTimer(timer);
|
||||
log('RENDER (' + (delay / 1e6).toFixed(3) + 'ms)');
|
||||
|
||||
switch(format) {
|
||||
case 'svg':
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
writeEncoded(encoding, svg);
|
||||
break;
|
||||
default:
|
||||
throw new HttpError(400, 'Unsupported image format');
|
||||
}
|
||||
}
|
||||
|
||||
const render = new RequestHandler('GET', MATCH_RENDER, handleRender);
|
||||
render.info = 'Rendering sequence diagrams at /render/';
|
||||
|
||||
module.exports = {render};
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const {Server} = require('./server/Server');
|
||||
const {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
||||
const {render} = require('./handlers/render');
|
||||
const {RenderRequestHandler} = require('./handlers/RenderRequestHandler');
|
||||
const path = require('path');
|
||||
|
||||
const DEV = process.argv.includes('dev');
|
||||
|
@ -31,7 +31,11 @@ function devMapper(file, type, data) {
|
|||
}
|
||||
}
|
||||
|
||||
const STATIC_MAX_AGE = 10 * 60; // 10 minutes
|
||||
const RENDER_MAX_AGE = 60 * 60 * 24 * 7; // 1 week
|
||||
|
||||
const statics = new StaticRequestHandler('')
|
||||
.setCacheMaxAge(DEV ? 0 : STATIC_MAX_AGE)
|
||||
.addMimeType('txt', 'text/plain; charset=utf-8')
|
||||
.addMimeType('htm', 'text/html; charset=utf-8')
|
||||
.addMimeType('html', 'text/html; charset=utf-8')
|
||||
|
@ -62,6 +66,10 @@ if(DEV) {
|
|||
statics.setFileWatch(true);
|
||||
}
|
||||
|
||||
const render = new RenderRequestHandler('/render')
|
||||
.setCacheMaxAge(DEV ? 0 : RENDER_MAX_AGE)
|
||||
.setCrossOrigin(true);
|
||||
|
||||
new Server()
|
||||
.addHandler(render)
|
||||
.addHandler(statics)
|
||||
|
|
|
@ -3,7 +3,31 @@ class RequestHandler {
|
|||
this.method = method;
|
||||
this.matcher = matcher;
|
||||
this.handleFn = handleFn;
|
||||
this.info = 'Custom handler at ' + this.method + ' ' + this.matcher;
|
||||
this.cacheMaxAge = 0;
|
||||
this.allowAllOrigins = false;
|
||||
this.info = `Custom handler at ${this.method} ${this.matcher}`;
|
||||
}
|
||||
|
||||
setCacheMaxAge(seconds) {
|
||||
this.cacheMaxAge = seconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
setCrossOrigin(allowAll) {
|
||||
this.allowAllOrigins = allowAll;
|
||||
return this;
|
||||
}
|
||||
|
||||
applyCommonHeaders(req, res) {
|
||||
if(this.allowAllOrigins) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
if(this.cacheMaxAge > 0) {
|
||||
res.setHeader(
|
||||
'Cache-Control',
|
||||
`public, max-age=${this.cacheMaxAge}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
apply(req, res, info) {
|
||||
|
|
|
@ -51,6 +51,7 @@ class StaticRequestHandler extends RequestHandler {
|
|||
_handleResource(req, res, resource, {pickEncoding, log}) {
|
||||
log('SERVE ' + resource.path);
|
||||
|
||||
this.applyCommonHeaders(req, res);
|
||||
const encoding = pickEncoding(
|
||||
PREF_ENCODINGS.filter((enc) => (resource.encodings[enc] !== null))
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue