| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- 'use strict';
- var Proto = require('uberproto');
- var zlib = require('zlib');
- var Q = require('q');
- var debug = require('debug')('connect-injector');
- var WritableStream = require('stream-buffers').WritableStreamBuffer;
- var each = function(obj, callback) {
- if(obj) {
- Object.keys(obj).forEach(function(key) {
- callback(obj[key], key);
- });
- }
- };
- module.exports = function (when, converter) {
- return function (req, res, next) {
- // Allows more than one injector
- if (res.injectors) {
- debug('injector already initialized, adding to existing');
- res.injectors.push({
- when: when,
- converter: converter
- });
- return next();
- }
- debug('initializing new injector');
- var oldWrite = res.write;
- // An object that we can mix into the response
- var mixin = {
- injectors: [
- {
- when: when,
- converter: converter
- }
- ],
- // Checks if this response should be intercepted
- _interceptCheck: function () {
- var self = this;
- if (typeof this._isIntercepted === 'undefined') {
- // Got through all injector objects
- each(res.injectors, function (obj) {
- if (obj.when(req, res)) {
- self._isIntercepted = true;
- obj.active = true;
- }
- });
- if (!this._isIntercepted) {
- this._isIntercepted = false;
- }
- debug('first time _interceptCheck ran', this._isIntercepted);
- }
- return this._isIntercepted;
- },
- // Overwrite setting the response headers. We can't do content-length
- // so lets just use transfer-encoding chunked
- setHeader: function (name, value) {
- if (name === 'content-length' || name === 'Content-Length') {
- debug('not setting content-length header');
- return;
- }
- return this._super(name, value);
- },
- // Overwrite writeHead since it can also set the headers and we need to override
- // the transfer-encoding
- writeHead: function(status, reasonPhrase, headers) {
- var self = this;
- each(headers || reasonPhrase, function(value, name) {
- self.setHeader(name, value);
- });
- return this._super(status, typeof reasonPhrase === 'string' ? reasonPhrase : undefined);
- },
- // Write into the buffer if this request is intercepted
- write: function (chunk, encoding) {
- if (this._interceptCheck()) {
- if(!this._interceptBuffer) {
- debug('initializing _interceptBuffer');
- this._interceptBuffer = new WritableStream();
- }
- return this._interceptBuffer.write(chunk, encoding);
- }
- return this._super.apply(this, arguments);
- },
- // End the request.
- end: function (data, encoding) {
- var self = this;
- var _super = this._super.bind(this);
- if (!this._interceptCheck()) {
- debug('not intercepting, ending with original .end');
- return _super(data, encoding);
- }
- if (data) {
- this.write(data, encoding);
- }
- debug('resetting to original response.write');
- this.write = oldWrite;
- // Responses without a body can just be ended
- if(!this._hasBody || !this._interceptBuffer) {
- debug('ending empty resopnse without injecting anything');
- return _super();
- }
- var gzipped = this.getHeader('content-encoding') === 'gzip';
- var chain = Q(this._interceptBuffer.getContents());
- if(gzipped) {
- debug('unzipping content');
- // Unzip the buffer
- chain = chain.then(function(buffer) {
- return Q.nfcall(zlib.gunzip, buffer);
- });
- }
- this.injectors.forEach(function(injector) {
- // Run all converters, if they are active
- // In series, using the previous output
- if(injector.active) {
- debug('adding injector to chain');
- var converter = injector.converter.bind(self);
- chain = chain.then(function(prev) {
- return Q.nfcall(converter, prev, req, res);
- });
- }
- });
- if(gzipped) {
- debug('re-zipping content');
- // Zip it again
- chain = chain.then(function(result) {
- return Q.nfcall(zlib.gzip, result);
- });
- }
- chain.then(_super).fail(function(e) {
- debug('injector chain failed, emitting error event');
- self.emit('error', e);
- });
- return true;
- }
- };
- Proto.mixin(mixin, res);
- return next();
- };
- };
|