SequenceDiagram/bin/server/encoding.js

78 lines
1.6 KiB
JavaScript

const {HttpError} = require('./HttpError');
const {Readable} = require('stream');
const zlib = require('zlib');
const MATCH_ACCEPT = new RegExp('^ *([^;]+)(?:;q=([0-9]+(?:\\.[0-9]+)?))? *$');
function parseAcceptEncoding(accept) {
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
const opts = (accept || 'identity;q=1, *;q=0.5').split(',');
const types = new Map();
opts.forEach((opt) => {
const match = MATCH_ACCEPT.exec(opt);
if(match) {
let q = Number.parseFloat(match[2] || '1');
if(q === 0) {
q = -1;
}
types.set(match[1], q);
}
});
if(!types.has('*')) {
if(!types.has('identity')) {
types.set('identity', -0.5);
}
types.set('*', -1);
}
return types;
}
function pickAcceptEncoding(types, preferred) {
let best = null;
let bestQ = -1;
const wildcard = types.get('*');
preferred.forEach((opt) => {
const q = types.get(opt) || wildcard;
if(q > bestQ) {
best = opt;
bestQ = q;
}
});
if(best === null) {
throw new HttpError(406, 'Not Acceptable');
}
return best;
}
function writeEncoded(res, encoding, compressionOpts, data) {
if(encoding === 'identity') {
res.end(data);
return;
}
res.setHeader('Content-Encoding', encoding);
const raw = new Readable();
raw.push(data, 'utf8');
raw.push(null);
switch(encoding) {
case 'gzip':
raw.pipe(zlib.createGzip(compressionOpts)).pipe(res);
break;
case 'deflate':
raw.pipe(zlib.createDeflate(compressionOpts)).pipe(res);
break;
default:
throw new HttpError(500, 'Failed to encode');
}
}
module.exports = {
parseAcceptEncoding,
pickAcceptEncoding,
writeEncoded,
};