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 {Server} = require('./server/Server');
|
||||||
const {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
const {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
||||||
const {render} = require('./handlers/render');
|
const {RenderRequestHandler} = require('./handlers/RenderRequestHandler');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const DEV = process.argv.includes('dev');
|
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('')
|
const statics = new StaticRequestHandler('')
|
||||||
|
.setCacheMaxAge(DEV ? 0 : STATIC_MAX_AGE)
|
||||||
.addMimeType('txt', 'text/plain; charset=utf-8')
|
.addMimeType('txt', 'text/plain; charset=utf-8')
|
||||||
.addMimeType('htm', 'text/html; charset=utf-8')
|
.addMimeType('htm', 'text/html; charset=utf-8')
|
||||||
.addMimeType('html', 'text/html; charset=utf-8')
|
.addMimeType('html', 'text/html; charset=utf-8')
|
||||||
|
@ -62,6 +66,10 @@ if(DEV) {
|
||||||
statics.setFileWatch(true);
|
statics.setFileWatch(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const render = new RenderRequestHandler('/render')
|
||||||
|
.setCacheMaxAge(DEV ? 0 : RENDER_MAX_AGE)
|
||||||
|
.setCrossOrigin(true);
|
||||||
|
|
||||||
new Server()
|
new Server()
|
||||||
.addHandler(render)
|
.addHandler(render)
|
||||||
.addHandler(statics)
|
.addHandler(statics)
|
||||||
|
|
|
@ -3,7 +3,31 @@ class RequestHandler {
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.matcher = matcher;
|
this.matcher = matcher;
|
||||||
this.handleFn = handleFn;
|
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) {
|
apply(req, res, info) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ class StaticRequestHandler extends RequestHandler {
|
||||||
_handleResource(req, res, resource, {pickEncoding, log}) {
|
_handleResource(req, res, resource, {pickEncoding, log}) {
|
||||||
log('SERVE ' + resource.path);
|
log('SERVE ' + resource.path);
|
||||||
|
|
||||||
|
this.applyCommonHeaders(req, res);
|
||||||
const encoding = pickEncoding(
|
const encoding = pickEncoding(
|
||||||
PREF_ENCODINGS.filter((enc) => (resource.encodings[enc] !== null))
|
PREF_ENCODINGS.filter((enc) => (resource.encodings[enc] !== null))
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue