mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
CachingMiddleware: Asyncify
This commit is contained in:
parent
0284d49522
commit
f86df5322e
2 changed files with 99 additions and 112 deletions
|
@ -31,7 +31,7 @@ const getTar = async () => {
|
||||||
exports.expressCreateServer = async (hookName, args) => {
|
exports.expressCreateServer = async (hookName, args) => {
|
||||||
// Cache both minified and static.
|
// Cache both minified and static.
|
||||||
const assetCache = new CachingMiddleware();
|
const assetCache = new CachingMiddleware();
|
||||||
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
|
args.app.all(/\/javascripts\/(.*)/, assetCache.handle.bind(assetCache));
|
||||||
|
|
||||||
// Minify will serve static files compressed (minify enabled). It also has
|
// Minify will serve static files compressed (minify enabled). It also has
|
||||||
// file-specific hacks for ace/require-kernel/etc.
|
// file-specific hacks for ace/require-kernel/etc.
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const async = require('async');
|
|
||||||
const Buffer = require('buffer').Buffer;
|
const Buffer = require('buffer').Buffer;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const fsp = fs.promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
const settings = require('./Settings');
|
const settings = require('./Settings');
|
||||||
const existsSync = require('./path_exists');
|
const existsSync = require('./path_exists');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The crypto module can be absent on reduced node installations.
|
* The crypto module can be absent on reduced node installations.
|
||||||
|
@ -79,6 +80,10 @@ if (_crypto) {
|
||||||
|
|
||||||
module.exports = class CachingMiddleware {
|
module.exports = class CachingMiddleware {
|
||||||
handle(req, res, next) {
|
handle(req, res, next) {
|
||||||
|
this._handle(req, res, next).catch((err) => next(err || new Error(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handle(req, res, next) {
|
||||||
if (!(req.method === 'GET' || req.method === 'HEAD') || !CACHE_DIR) {
|
if (!(req.method === 'GET' || req.method === 'HEAD') || !CACHE_DIR) {
|
||||||
return next(undefined, req, res);
|
return next(undefined, req, res);
|
||||||
}
|
}
|
||||||
|
@ -92,125 +97,107 @@ module.exports = class CachingMiddleware {
|
||||||
const url = new URL(req.url, 'http://localhost');
|
const url = new URL(req.url, 'http://localhost');
|
||||||
const cacheKey = generateCacheKey(url.pathname + url.search);
|
const cacheKey = generateCacheKey(url.pathname + url.search);
|
||||||
|
|
||||||
fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => {
|
const stats = await fsp.stat(`${CACHE_DIR}minified_${cacheKey}`).catch(() => {});
|
||||||
const modifiedSince = (req.headers['if-modified-since'] &&
|
const modifiedSince =
|
||||||
new Date(req.headers['if-modified-since']));
|
req.headers['if-modified-since'] && new Date(req.headers['if-modified-since']);
|
||||||
const lastModifiedCache = !error && stats.mtime;
|
if (stats != null && stats.mtime && responseCache[cacheKey]) {
|
||||||
if (lastModifiedCache && responseCache[cacheKey]) {
|
req.headers['if-modified-since'] = stats.mtime.toUTCString();
|
||||||
req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
|
} else {
|
||||||
|
delete req.headers['if-modified-since'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always issue get to downstream.
|
||||||
|
oldReq.method = req.method;
|
||||||
|
req.method = 'GET';
|
||||||
|
|
||||||
|
// This handles read/write synchronization as well as its predecessor,
|
||||||
|
// which is to say, not at all.
|
||||||
|
// TODO: Implement locking on write or ditch caching of gzip and use
|
||||||
|
// existing middlewares.
|
||||||
|
const respond = () => {
|
||||||
|
req.method = oldReq.method || req.method;
|
||||||
|
res.write = oldRes.write || res.write;
|
||||||
|
res.end = oldRes.end || res.end;
|
||||||
|
|
||||||
|
const headers = {};
|
||||||
|
Object.assign(headers, (responseCache[cacheKey].headers || {}));
|
||||||
|
const statusCode = responseCache[cacheKey].statusCode;
|
||||||
|
|
||||||
|
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
|
||||||
|
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
|
||||||
|
pathStr += '.gz';
|
||||||
|
headers['content-encoding'] = 'gzip';
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastModified = headers['last-modified'] && new Date(headers['last-modified']);
|
||||||
|
|
||||||
|
if (statusCode === 200 && lastModified <= modifiedSince) {
|
||||||
|
res.writeHead(304, headers);
|
||||||
|
res.end();
|
||||||
|
} else if (req.method === 'GET') {
|
||||||
|
const readStream = fs.createReadStream(pathStr);
|
||||||
|
res.writeHead(statusCode, headers);
|
||||||
|
readStream.pipe(res);
|
||||||
} else {
|
} else {
|
||||||
delete req.headers['if-modified-since'];
|
res.writeHead(statusCode, headers);
|
||||||
|
res.end();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Always issue get to downstream.
|
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
|
||||||
oldReq.method = req.method;
|
if (expirationDate > new Date()) {
|
||||||
req.method = 'GET';
|
// Our cached version is still valid.
|
||||||
|
return respond();
|
||||||
|
}
|
||||||
|
|
||||||
// This handles read/write synchronization as well as its predecessor,
|
const _headers = {};
|
||||||
// which is to say, not at all.
|
oldRes.setHeader = res.setHeader;
|
||||||
// TODO: Implement locking on write or ditch caching of gzip and use
|
res.setHeader = (key, value) => {
|
||||||
// existing middlewares.
|
// Don't set cookies, see issue #707
|
||||||
const respond = () => {
|
if (key.toLowerCase() === 'set-cookie') return;
|
||||||
req.method = oldReq.method || req.method;
|
|
||||||
res.write = oldRes.write || res.write;
|
|
||||||
res.end = oldRes.end || res.end;
|
|
||||||
|
|
||||||
const headers = {};
|
_headers[key.toLowerCase()] = value;
|
||||||
Object.assign(headers, (responseCache[cacheKey].headers || {}));
|
oldRes.setHeader.call(res, key, value);
|
||||||
const statusCode = responseCache[cacheKey].statusCode;
|
};
|
||||||
|
|
||||||
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
|
oldRes.writeHead = res.writeHead;
|
||||||
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
|
res.writeHead = (status, headers) => {
|
||||||
pathStr += '.gz';
|
res.writeHead = oldRes.writeHead;
|
||||||
headers['content-encoding'] = 'gzip';
|
if (status === 200) {
|
||||||
}
|
// Update cache
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
const lastModified = (headers['last-modified'] &&
|
Object.keys(headers || {}).forEach((key) => {
|
||||||
new Date(headers['last-modified']));
|
res.setHeader(key, headers[key]);
|
||||||
|
});
|
||||||
|
headers = _headers;
|
||||||
|
|
||||||
if (statusCode === 200 && lastModified <= modifiedSince) {
|
oldRes.write = res.write;
|
||||||
res.writeHead(304, headers);
|
oldRes.end = res.end;
|
||||||
res.end();
|
res.write = (data, encoding) => {
|
||||||
} else if (req.method === 'GET') {
|
buffer += data.toString(encoding);
|
||||||
const readStream = fs.createReadStream(pathStr);
|
};
|
||||||
res.writeHead(statusCode, headers);
|
res.end = async (data, encoding) => {
|
||||||
readStream.pipe(res);
|
await Promise.all([
|
||||||
} else {
|
fsp.writeFile(`${CACHE_DIR}minified_${cacheKey}`, buffer).catch(() => {}),
|
||||||
res.writeHead(statusCode, headers);
|
util.promisify(zlib.gzip)(buffer)
|
||||||
res.end();
|
.then((content) => fsp.writeFile(`${CACHE_DIR}minified_${cacheKey}.gz`, content))
|
||||||
}
|
.catch(() => {}),
|
||||||
};
|
]);
|
||||||
|
responseCache[cacheKey] = {statusCode: status, headers};
|
||||||
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
|
respond();
|
||||||
if (expirationDate > new Date()) {
|
};
|
||||||
// Our cached version is still valid.
|
} else if (status === 304) {
|
||||||
return respond();
|
// Nothing new changed from the cached version.
|
||||||
|
oldRes.write = res.write;
|
||||||
|
oldRes.end = res.end;
|
||||||
|
res.write = (data, encoding) => {};
|
||||||
|
res.end = (data, encoding) => { respond(); };
|
||||||
|
} else {
|
||||||
|
res.writeHead(status, headers);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const _headers = {};
|
next(undefined, req, res);
|
||||||
oldRes.setHeader = res.setHeader;
|
|
||||||
res.setHeader = (key, value) => {
|
|
||||||
// Don't set cookies, see issue #707
|
|
||||||
if (key.toLowerCase() === 'set-cookie') return;
|
|
||||||
|
|
||||||
_headers[key.toLowerCase()] = value;
|
|
||||||
oldRes.setHeader.call(res, key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
oldRes.writeHead = res.writeHead;
|
|
||||||
res.writeHead = (status, headers) => {
|
|
||||||
res.writeHead = oldRes.writeHead;
|
|
||||||
if (status === 200) {
|
|
||||||
// Update cache
|
|
||||||
let buffer = '';
|
|
||||||
|
|
||||||
Object.keys(headers || {}).forEach((key) => {
|
|
||||||
res.setHeader(key, headers[key]);
|
|
||||||
});
|
|
||||||
headers = _headers;
|
|
||||||
|
|
||||||
oldRes.write = res.write;
|
|
||||||
oldRes.end = res.end;
|
|
||||||
res.write = (data, encoding) => {
|
|
||||||
buffer += data.toString(encoding);
|
|
||||||
};
|
|
||||||
res.end = (data, encoding) => {
|
|
||||||
async.parallel([
|
|
||||||
(callback) => {
|
|
||||||
const path = `${CACHE_DIR}minified_${cacheKey}`;
|
|
||||||
fs.writeFile(path, buffer, (error, stats) => {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(callback) => {
|
|
||||||
const path = `${CACHE_DIR}minified_${cacheKey}.gz`;
|
|
||||||
zlib.gzip(buffer, (error, content) => {
|
|
||||||
if (error) {
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
fs.writeFile(path, content, (error, stats) => {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
], () => {
|
|
||||||
responseCache[cacheKey] = {statusCode: status, headers};
|
|
||||||
respond();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else if (status === 304) {
|
|
||||||
// Nothing new changed from the cached version.
|
|
||||||
oldRes.write = res.write;
|
|
||||||
oldRes.end = res.end;
|
|
||||||
res.write = (data, encoding) => {};
|
|
||||||
res.end = (data, encoding) => { respond(); };
|
|
||||||
} else {
|
|
||||||
res.writeHead(status, headers);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
next(undefined, req, res);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue