return function session(req, res, next) { // self-awareness if (req.session) return next(); // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); // pathname mismatch var originalPath = parseUrl.original(req).pathname; if (0 != originalPath.indexOf(cookie.path || '/')) return next(); // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware var secret = options.secret || req.secret; // ensure secret is available or bail if (!secret) next(new Error('`secret` option required for sessions')); var originalHash , originalId; // expose store req.sessionStore = store; // get the session ID from the cookie var cookieId = req.sessionID = getcookie(req, name, secret); // set-cookie onHeaders(res, function(){ if (!req.session) { debug('no session'); return; } var cookie = req.session.cookie; // only send secure cookies via https if (cookie.secure && !issecure(req, trustProxy)) { debug('not secured'); return; } if (!shouldSetCookie(req)) { return; } setcookie(res, name, req.sessionID, secret, cookie.data); }); // proxy end() to commit the session var _end = res.end; var _write = res.write; var ended = false; res.end = function end(chunk, encoding) { if (ended) { return false; } ended = true; var ret; var sync = true; if (chunk === undefined || chunk === null) { chunk = ''; } function writeend() { if (sync) { ret = _end.call(res, chunk, encoding); sync = false; return; } _end.call(res); } function writetop() { if (!sync) { return ret; } var contentLength = Number(res.getHeader('Content-Length')); if (!isNaN(contentLength) && contentLength > 0) { // measure chunk chunk = !Buffer.isBuffer(chunk) ? new Buffer(chunk, encoding) : chunk; encoding = undefined; if (chunk.length !== 0) { debug('split response'); ret = _write.call(res, chunk.slice(0, chunk.length - 1)); chunk = chunk.slice(chunk.length - 1, chunk.length); return ret; } } ret = _write.call(res, chunk, encoding); sync = false; return ret; } if (shouldDestroy(req)) { // destroy session debug('destroying'); store.destroy(req.sessionID, function ondestroy(err) { if (err) { defer(next, err); } debug('destroyed'); writeend(); }); return writetop(); } // no session to save if (!req.session) { debug('no session'); return _end.call(res, chunk, encoding); } req.session.resetMaxAge(); if (shouldSave(req)) { debug('saving'); req.session.save(function onsave(err) { if (err) { defer(next, err); } debug('saved'); writeend(); }); return writetop(); } return _end.call(res, chunk, encoding); }; // generate the session function generate() { store.generate(req); originalId = req.sessionID; originalHash = hash(req.session); } // check if session has been modified function isModified(sess) { return originalHash != hash(sess) || originalId != sess.id; } // determine if session should be destroyed function shouldDestroy(req) { return req.sessionID && unsetDestroy && req.session == null; } // determine if session should be saved to store function shouldSave(req) { return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) : resaveSession || isModified(req.session); } // determine if cookie should be set on response function shouldSetCookie(req) { // in case of rolling session, always reset the cookie if (rollingSessions) { return true; } return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) : req.session.cookie.expires != null && isModified(req.session); } // generate a session if the browser doesn't send a sessionID if (!req.sessionID) { debug('no SID sent, generating session'); generate(); next(); return; } // generate the session object debug('fetching %s', req.sessionID); store.get(req.sessionID, function(err, sess){ // error handling if (err) { debug('error %j', err); if ('ENOENT' == err.code) { generate(); next(); } else { next(err); } // no session } else if (!sess) { debug('no session found'); generate(); next(); // populate req.session } else { debug('session found'); store.createSession(req, sess); originalId = req.sessionID; originalHash = hash(sess); next(); } }); };
return function session(req, res, next) { // self-awareness if (req.session) return next(); // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); // pathname mismatch var originalPath = parse(req.originalUrl).pathname; if (0 != originalPath.indexOf(cookie.path || '/')) return next(); // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware var secret = options.secret || req.secret; // ensure secret is available or bail if (!secret) throw new Error('`secret` option required for sessions'); var originalHash , originalId; // expose store req.sessionStore = store; // grab the session cookie value and check the signature var rawCookie = req.cookies[name]; // get signedCookies for backwards compat with signed cookies var unsignedCookie = req.signedCookies[name]; if (!unsignedCookie && rawCookie) { unsignedCookie = (0 == rawCookie.indexOf('s:')) ? signature.unsign(rawCookie.slice(2), secret) : rawCookie; } // set-cookie onHeaders(res, function(){ if (!req.session) { debug('no session'); return; } var cookie = req.session.cookie , proto = (req.headers['x-forwarded-proto'] || '').split(',')[0].toLowerCase().trim() , tls = req.connection.encrypted || (trustProxy && 'https' == proto) , isNew = unsignedCookie != req.sessionID; // only send secure cookies via https if (cookie.secure && !tls) { debug('not secured'); return; } // in case of rolling session, always reset the cookie if (!rollingSessions) { // browser-session length cookie if (null == cookie.expires) { if (!isNew) { debug('already set browser-session cookie'); return } // compare hashes and ids } else if (originalHash == hash(req.session) && originalId == req.session.id) { debug('unmodified session'); return } } var val = 's:' + signature.sign(req.sessionID, secret); debug('set-cookie %s', val); res.cookie(name, val, cookie.data); }); // proxy end() to commit the session var end = res.end; res.end = function(data, encoding){ res.end = end; if (!req.session) return res.end(data, encoding); debug('saving'); req.session.resetMaxAge(); req.session.save(function(err){ if (err) console.error(err.stack); debug('saved'); res.end(data, encoding); }); }; // generate the session function generate() { store.generate(req); } // get the sessionID from the cookie req.sessionID = unsignedCookie; // generate a session if the browser doesn't send a sessionID if (!req.sessionID) { debug('no SID sent, generating session'); generate(); next(); return; } // generate the session object debug('fetching %s', req.sessionID); store.get(req.sessionID, function(err, sess){ // error handling if (err) { debug('error %j', err); if ('ENOENT' == err.code) { generate(); next(); } else { next(err); } // no session } else if (!sess) { debug('no session found'); generate(); next(); // populate req.session } else { debug('session found'); store.createSession(req, sess); originalId = req.sessionID; originalHash = hash(sess); next(); } }); };
return function cookieSession(req, res, next){ var cookies = req.sessionCookies = new Cookies(req, res, keys); var sess, json; // to pass to Session() req.sessionOptions = {}; for (var key in opts) { req.sessionOptions[key] = opts[key]; }; req.sessionKey = name; req.__defineGetter__('session', function(){ // already retrieved if (sess) return sess; // unset if (false === sess) return null; json = cookies.get(name, opts); if (json) { debug('parse %s', json); try { sess = new Session(req, decode(json)); } catch (err) { // backwards compatibility: // create a new session if parsing fails. // new Buffer(string, 'base64') does not seem to crash // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. if (!(err instanceof SyntaxError)) throw err; sess = new Session(req); } } else { debug('new session'); sess = new Session(req); } return sess; }); req.__defineSetter__('session', function(val){ if (null == val) return sess = false; if ('object' == typeof val) return sess = new Session(req, val); throw new Error('req.session can only be set as null or an object.'); }); onHeaders(res, function setHeaders() { if (sess === undefined) { // not accessed return; } try { if (sess === false) { // remove cookies.set(name, '', opts); } else if (!json && !sess.length) { // do nothing if new and not populated } else if (sess.changed(json)) { // save sess.save(); } } catch (e) { debug('error saving session %s', e.message); } }); next(); }
return function compression(req, res, next){ var ended = false var length var listeners = [] var write = res.write var on = res.on var end = res.end var stream // flush res.flush = function flush() { if (stream) { stream.flush() } } // proxy res.write = function(chunk, encoding){ if (ended) { return false } if (!this._header) { this._implicitHeader() } return stream ? stream.write(new Buffer(chunk, encoding)) : write.call(this, chunk, encoding) }; res.end = function(chunk, encoding){ if (ended) { return false } if (!this._header) { // estimate the length if (!this.getHeader('Content-Length')) { length = chunkLength(chunk, encoding) } this._implicitHeader() } if (!stream) { return end.call(this, chunk, encoding) } // mark ended ended = true // write Buffer for Node.js 0.8 return chunk ? stream.end(new Buffer(chunk, encoding)) : stream.end() }; res.on = function(type, listener){ if (!listeners || type !== 'drain') { return on.call(this, type, listener) } if (stream) { return stream.on(type, listener) } // buffer listeners for future stream listeners.push([type, listener]) return this } function nocompress(msg) { debug('no compression: %s', msg) addListeners(res, on, listeners) listeners = null } onHeaders(res, function(){ // determine if request is filtered if (!filter(req, res)) { nocompress('filtered') return } // determine if the entity should be transformed if (!shouldTransform(req, res)) { nocompress('no transform') return } // vary vary(res, 'Accept-Encoding') // content-length below threshold if (Number(res.getHeader('Content-Length')) < threshold || length < threshold) { nocompress('size below threshold') return } var encoding = res.getHeader('Content-Encoding') || 'identity'; // already encoded if ('identity' !== encoding) { nocompress('already encoded') return } // head if ('HEAD' === req.method) { nocompress('HEAD request') return } var contentType = res.getHeader('Content-Type'); // compression method var accept = accepts(req) // send in each compression method separately to ignore client preference and // instead enforce server preference. also, server-sent events (mime type of // text/event-stream) require flush functionality, so skip brotli in that // case. var method = (contentType !== "text/event-stream" && accept.encoding('br')) || accept.encoding('gzip') || accept.encoding('deflate') || accept.encoding('identity'); // negotiation failed if (!method || method === 'identity') { nocompress('not acceptable') return } // do we have this coding/url/etag combo in the cache? var etag = res.getHeader('ETag') || null; var cacheable = cache && shouldCache(req, res) && etag && res.statusCode >= 200 && res.statusCode < 300 if (cacheable) { var buffer = cache.lookup(method, req.url, etag) if (buffer) { // the rest of the code expects a duplex stream, so // make a duplex stream that just ignores its input stream = new BufferDuplex(buffer) } } // if stream is not assigned, we got a cache miss and need to compress // the result if (!stream) { // compression stream debug('%s compression', method) switch (method) { case 'br': stream = iltorb.compressStream(brotliOpts) // brotli has no flush method. add a dummy flush method here. stream.flush = dummyBrotliFlush; break case 'gzip': stream = zlib.createGzip(zlibOpts) break case 'deflate': stream = zlib.createDeflate(zlibOpts) break } // if it is cacheable, let's keep hold of the compressed chunks and cache // them once the compression stream ends. if (cacheable) { var chunks = []; stream.on('data', function (chunk){ chunks.push(chunk) }) stream.on('end', function () { cache.add(method, req.url, etag, chunks) }) } } // add buffered listeners to stream addListeners(stream, stream.on, listeners) // header fields res.setHeader('Content-Encoding', method); res.removeHeader('Content-Length'); // compression stream.on('data', function(chunk){ if (write.call(res, chunk) === false) { stream.pause() } }); stream.on('end', function(){ end.call(res); }); on.call(res, 'drain', function() { stream.resume() }); }); next(); };
log.request = function(req, res, next){ for(let i = 0; i < opts.req_filter.length; i++){ if(req.originalUrl.indexOf(opts.req_filter[i]) > -1) return next(); } onHeaders(res, log_start.bind(res)); onFinished(res, req_log); next(); // ========= function log_start(){ this._log_start = process.hrtime(); } function req_log(){ const sc = res.statusCode < 400 ? 'green' : 'red'; const ms = nano_time(res._log_start); const user = req._log_user ? chalk.blue(req._log_user) : chalk.grey(req.ip); let ref = req.get('referrer') || req.get('referer'); if(ref){ ref = ref.replace('http://', ''); ref = ref.replace('https://', ''); } // Args const args = [chalk.cyan('REQUEST'),'|']; if(opts.date) args.push(log.date(),'|'); args.push(user); if(ref) args.push(chalk.grey(`from ${ref}`)); args.push('|'); args.push(req.method, chalk[sc](res.statusCode), ':', `${ms} ms`, '|', chalk.magenta(req.originalUrl) ); console.log.apply(log, args); // === function nano_time(start){ let t = conv(process.hrtime()) - conv(start); // ns t = Math.round(t / 1000); // µs return t / 1000; // ms [3 dec] // ==== function conv(t){ if(!t | typeof t[0] === 'undefined') return 0; return t[0] * 1e9 + t[1]; } } } };
return function compression(req, res, next){ var compress = true var listeners = [] var write = res.write var on = res.on var end = res.end var stream // see #8 req.on('close', function(){ res.write = res.end = function(){}; }); // flush is noop by default res.flush = noop; // proxy res.write = function(chunk, encoding){ if (!this._header) { // if content-length is set and is lower // than the threshold, don't compress var len = Number(res.getHeader('Content-Length')) checkthreshold(len) this._implicitHeader(); } return stream ? stream.write(new Buffer(chunk, encoding)) : write.call(res, chunk, encoding); }; res.end = function(chunk, encoding){ var len if (chunk) { len = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk, encoding) } if (!this._header) { checkthreshold(len) } if (chunk) { this.write(chunk, encoding); } return stream ? stream.end() : end.call(res); }; res.on = function(type, listener){ if (!listeners || type !== 'drain') { return on.call(this, type, listener) } if (stream) { return stream.on(type, listener) } // buffer listeners for future stream listeners.push([type, listener]) return this } function checkthreshold(len) { if (compress && len < threshold) { debug('size below threshold') compress = false } } function nocompress(msg) { debug('no compression' + (msg ? ': ' + msg : '')) addListeners(res, on, listeners) listeners = null } onHeaders(res, function(){ // determine if request is filtered if (!filter(req, res)) { nocompress('filtered') return } // vary vary(res, 'Accept-Encoding') if (!compress) { nocompress() return } var encoding = res.getHeader('Content-Encoding') || 'identity'; // already encoded if ('identity' !== encoding) { nocompress('already encoded') return } // head if ('HEAD' === req.method) { nocompress('HEAD request') return } // compression method var accept = accepts(req); var method = accept.encodings(['gzip', 'deflate', 'identity']); // negotiation failed if (!method || method === 'identity') { nocompress('not acceptable') return } // compression stream debug('%s compression', method) stream = exports.methods[method](options); addListeners(stream, stream.on, listeners) // overwrite the flush method res.flush = function(){ stream.flush(); } // header fields res.setHeader('Content-Encoding', method); res.removeHeader('Content-Length'); // compression stream.on('data', function(chunk){ if (write.call(res, chunk) === false) { stream.pause() } }); stream.on('end', function(){ end.call(res); }); on.call(res, 'drain', function() { stream.resume() }); }); next(); };
return function cookieSession(req, res, next) { // req.secret is for backwards compatibility var secret = options.secret || req.secret; if (!secret) throw new Error('`secret` option required for cookie sessions'); // default session req.session = {}; var cookie = req.session.cookie = new Cookie(options.cookie); // pathname mismatch var originalPath = parseUrl.original(req).pathname; if (0 != originalPath.indexOf(cookie.path)) return next(); // cookieParser secret if (!options.secret && req.secret) { req.session = req.signedCookies[key] || {}; req.session.cookie = cookie; } else { // TODO: refactor var rawCookie = req.cookies[key]; if (rawCookie) { var unsigned = cookieParser.signedCookie(rawCookie, secret); if (unsigned) { var original = unsigned; req.session = cookieParser.JSONCookie(unsigned) || {}; req.session.cookie = cookie; } } } onHeaders(res, function(){ // removed if (!req.session) { debug('clear session'); cookie.expires = new Date(0); res.setHeader('Set-Cookie', cookie.serialize(key, '')); return; } delete req.session.cookie; // check security var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase() , tls = req.connection.encrypted || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0]); // only send secure cookies via https if (cookie.secure && !tls) return debug('not secured'); // serialize debug('serializing %j', req.session); var val = 'j:' + JSON.stringify(req.session); // compare data, no need to set-cookie if unchanged if (original == val) return debug('unmodified session'); // set-cookie val = 's:' + signature.sign(val, secret); val = cookie.serialize(key, val); debug('set-cookie %j', cookie); res.setHeader('Set-Cookie', val); }); next(); };
function noCache(res) { onHeaders(res, function () { this.removeHeader('ETag') }) return res }
return function compression(req, res, next) { var ended = false; var length; var listeners = []; var write = res.write; var on = res.on; var end = res.end; var stream; // flush res.flush = function flush() { if (stream) { stream.flush(); } }; // proxy res.write = function(chunk, encoding) { if (ended === true) { return false; } if (!this._header) { this._implicitHeader(); } return stream ? stream.write(new Buffer(chunk, encoding)) : write.call( res, chunk, encoding); }; res.end = function(chunk, encoding) { if (ended === true) { return false; } if (!this._header) { // estimate the length if (!this.getHeader('Content-Length')) { length = chunkLength(chunk, encoding); } this._implicitHeader(); } if (!stream) { return end.call(this, chunk, encoding); } // mark ended ended = true; // write Buffer for Node.js 0.8 return chunk ? stream.end(new Buffer(chunk, encoding)) : stream.end(); }; res.on = function(type, listener) { if (!listeners || type !== 'drain') { return on.call(this, type, listener); } if (stream) { return stream.on(type, listener); } // buffer listeners for future stream listeners.push([ type, listener ]); return this; }; function nocompress(msg) { debug('no compression: %s', msg); addListeners(res, on, listeners); listeners = null; return; } onHeaders(res, function() { // determine if request is filtered if (!filter(req, res)) { return nocompress('filtered'); } // determine if the entity should be transformed if (!shouldTransform(req, res)) { nocompress('no transform'); return; } // vary vary(res, 'Accept-Encoding'); // content-length below threshold if (Number(res.getHeader('Content-Length')) < threshold || length < threshold) { nocompress('size below threshold'); return; } var encoding = res.getHeader('Content-Encoding') || 'identity'; if ('identity' !== encoding) { // already encoded return nocompress('already encoded'); } else if ('HEAD' === req.method) { // head return nocompress('HEAD request'); } // compression method var accept = accepts(req); var method = accept.encoding(available); // negotiation failed if (!method || method === 'identity') { return nocompress('not acceptable'); } // compression stream if (method === 'gzip') { stream = zlib.createGzip(zlib_options); } else if (method === 'deflate') { stream = zlib.createDeflate(zlib_options); } else { return nocompress('not acceptable'); } debug('%s compression', method); // add bufferred listeners to stream addListeners(stream, stream.on, listeners); // header fields res.setHeader('Content-Encoding', method); res.removeHeader('Content-Length'); // compression stream.on('data', function(chunk) { if (write.call(res, chunk) === false) { stream.pause(); } return; }).on('end', function() { return end.call(res); }); on.call(res, 'drain', function() { return stream.resume(); }); }); return next(); };
return function session(req, res, next) { // self-awareness if (req.session) return next(); // Handle connection as if there is no session if // the store has temporarily disconnected etc if (!storeReady) return debug('store is disconnected'), next(); // pathname mismatch var originalPath = parseUrl.original(req).pathname; if (0 != originalPath.indexOf(cookie.path || '/')) return next(); // ensure a secret is available or bail if (!secret && !req.secret) { next(new Error('secret option required for sessions')); return; } // backwards compatibility for signed cookies // req.secret is passed from the cookie parser middleware var secrets = secret || [req.secret]; var originalHash; var originalId; var savedHash; // expose store req.sessionStore = store; // get the session ID from the cookie var cookieId = req.sessionID = getcookie(req, name, secrets); // set-cookie onHeaders(res, function(){ if (!req.session) { debug('no session'); return; } var cookie = req.session.cookie; // only send secure cookies via https if (cookie.secure && !issecure(req, trustProxy)) { debug('not secured'); return; } if (!shouldSetCookie(req)) { return; } setcookie(res, name, req.sessionID, secrets[0], cookie.data); }); // proxy end() to commit the session var _end = res.end; var _write = res.write; var ended = false; res.end = function end(chunk, encoding) { if (ended) { return false; } ended = true; var ret; var sync = true; function writeend() { if (sync) { ret = _end.call(res, chunk, encoding); sync = false; return; } _end.call(res); } function writetop() { if (!sync) { return ret; } if (chunk == null) { ret = true; return ret; } var contentLength = Number(res.getHeader('Content-Length')); if (!isNaN(contentLength) && contentLength > 0) { // measure chunk chunk = !Buffer.isBuffer(chunk) ? new Buffer(chunk, encoding) : chunk; encoding = undefined; if (chunk.length !== 0) { debug('split response'); ret = _write.call(res, chunk.slice(0, chunk.length - 1)); chunk = chunk.slice(chunk.length - 1, chunk.length); return ret; } } ret = _write.call(res, chunk, encoding); sync = false; return ret; } if (shouldDestroy(req)) { // destroy session debug('destroying'); store.destroy(req.sessionID, function ondestroy(err) { if (err) { defer(next, err); } debug('destroyed'); writeend(); }); return writetop(); } // no session to save if (!req.session) { debug('no session'); return _end.call(res, chunk, encoding); } // touch session req.session.touch(); if (shouldSave(req)) { req.session.save(function onsave(err) { if (err) { defer(next, err); } writeend(); }); return writetop(); } else if (storeImplementsTouch && shouldTouch(req)) { // store implements touch method debug('touching'); store.touch(req.sessionID, req.session, function ontouch(err) { if (err) { defer(next, err); } debug('touched'); writeend(); }); return writetop(); } return _end.call(res, chunk, encoding); }; // generate the session function generate() { store.generate(req); originalId = req.sessionID; originalHash = hash(req.session); wrapmethods(req.session); } // wrap session methods function wrapmethods(sess) { var _save = sess.save; function save() { debug('saving %s', this.id); savedHash = hash(this); _save.apply(this, arguments); } Object.defineProperty(sess, 'save', { configurable: true, enumerable: false, value: save, writable: true }); } // check if session has been modified function isModified(sess) { return originalId !== sess.id || originalHash !== hash(sess); } // check if session has been saved function isSaved(sess) { return originalId === sess.id && savedHash === hash(sess); } // determine if session should be destroyed function shouldDestroy(req) { return req.sessionID && unsetDestroy && req.session == null; } // determine if session should be saved to store function shouldSave(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { debug('session ignored because of bogus req.sessionID %o', req.sessionID); return false; } return !saveUninitializedSession && cookieId !== req.sessionID ? isModified(req.session) : !isSaved(req.session) } // determine if session should be touched function shouldTouch(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { debug('session ignored because of bogus req.sessionID %o', req.sessionID); return false; } return cookieId === req.sessionID && !shouldSave(req); } // determine if cookie should be set on response function shouldSetCookie(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { return false; } // in case of rolling session, always reset the cookie if (rollingSessions) { return true; } return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) : req.session.cookie.expires != null && isModified(req.session); } // generate a session if the browser doesn't send a sessionID if (!req.sessionID) { debug('no SID sent, generating session'); generate(); next(); return; } // generate the session object debug('fetching %s', req.sessionID); store.get(req.sessionID, function(err, sess){ // error handling if (err) { debug('error %j', err); if (err.code !== 'ENOENT') { next(err); return; } generate(); // no session } else if (!sess) { debug('no session found'); generate(); // populate req.session } else { debug('session found'); store.createSession(req, sess); originalId = req.sessionID; originalHash = hash(sess); if (!resaveSession) { savedHash = originalHash } wrapmethods(req.session); } next(); }); };
return function compression (req, res, next) { var ended = false var length var listeners = [] var stream var _end = res.end var _on = res.on var _write = res.write // flush res.flush = function flush () { if (stream) { stream.flush() } } // proxy res.write = function write (chunk, encoding) { if (ended) { return false } if (!this._header) { this._implicitHeader() } return stream ? stream.write(toBuffer(chunk, encoding)) : _write.call(this, chunk, encoding) } res.end = function end (chunk, encoding) { if (ended) { return false } if (!this._header) { // estimate the length if (!this.getHeader('Content-Length')) { length = chunkLength(chunk, encoding) } this._implicitHeader() } if (!stream) { return _end.call(this, chunk, encoding) } // mark ended ended = true // write Buffer for Node.js 0.8 return chunk ? stream.end(toBuffer(chunk, encoding)) : stream.end() } res.on = function on (type, listener) { if (!listeners || type !== 'drain') { return _on.call(this, type, listener) } if (stream) { return stream.on(type, listener) } // buffer listeners for future stream listeners.push([type, listener]) return this } function nocompress (msg) { debug('no compression: %s', msg) addListeners(res, _on, listeners) listeners = null } onHeaders(res, function onResponseHeaders () { // determine if request is filtered if (!filter(req, res)) { nocompress('filtered') return } // determine if the entity should be transformed if (!shouldTransform(req, res)) { nocompress('no transform') return } // vary vary(res, 'Accept-Encoding') // content-length below threshold if (Number(res.getHeader('Content-Length')) < threshold || length < threshold) { nocompress('size below threshold') return } var encoding = res.getHeader('Content-Encoding') || 'identity' // already encoded if (encoding !== 'identity') { nocompress('already encoded') return } // head if (req.method === 'HEAD') { nocompress('HEAD request') return } // compression method var accept = accepts(req) var method = accept.encoding(['gzip', 'deflate', 'identity']) // we really don't prefer deflate if (method === 'deflate' && accept.encoding(['gzip'])) { method = accept.encoding(['gzip', 'identity']) } // negotiation failed if (!method || method === 'identity') { nocompress('not acceptable') return } // compression stream debug('%s compression', method) stream = method === 'gzip' ? zlib.createGzip(opts) : zlib.createDeflate(opts) // add buffered listeners to stream addListeners(stream, stream.on, listeners) // header fields res.setHeader('Content-Encoding', method) res.removeHeader('Content-Length') // compression stream.on('data', function onStreamData (chunk) { if (_write.call(res, chunk) === false) { stream.pause() } }) stream.on('end', function onStreamEnd () { _end.call(res) }) _on.call(res, 'drain', function onResponseDrain () { stream.resume() }) }) next() }
return function(req, res, next) { onHeaders(res, function onHeaders() { res.removeHeader(toStrip); }); next(); };