Jump To …

response.js

The Response object encapsulates a Node.js HTTP response.

var Content = require("./content")
  , HeaderMixins = require("./mixins/headers")
  , CookieJarLib = require( "cookiejar" )
  , Cookie = CookieJarLib.Cookie
;

Browser doesn't have zlib.

var zlib = null;
try {
  zlib = require('zlib');
} catch (e) {
  console.warn("no zlib library");
}

Iconv doesn't work in browser

var Iconv = null;
try {
  Iconv = require('iconv-lite');
} catch (e) {
  console.warn("no iconv library");
}

Construct a Response object. You should never have to do this directly. The Request object handles this, getting the raw response object and passing it in here, along with the request. The callback allows us to stream the response and then use the callback to let the request know when it's ready.

var Response = function(raw, request, callback) { 
  var response = this;
  this._raw = raw;

The ._setHeaders method is "private"; you can't otherwise set headers on the response.

  this._setHeaders.call(this,raw.headers);
  

store any cookies

  if (request.cookieJar && this.getHeader('set-cookie')) {
    var cookieStrings = this.getHeader('set-cookie');
    var cookieObjs = []
      , cookie;

    for (var i = 0; i < cookieStrings.length; i++) {
      var cookieString = cookieStrings[i];
      if (!cookieString) {
        continue;
      }

      if (!cookieString.match(/domain\=/i)) {
        cookieString += '; domain=' + request.host;
      }

      if (!cookieString.match(/path\=/i)) {
        cookieString += '; path=' + request.path;
      }

      try {
        cookie = new Cookie(cookieString);
        if (cookie) {
          cookieObjs.push(cookie);
        }
      } catch (e) {
        console.warn("Tried to set bad cookie: " + cookieString);
      }
    }

    request.cookieJar.setCookies(cookieObjs);
  }

  this.request = request;
  this.client = request.client;
  this.log = this.request.log;

Stream the response content entity and fire the callback when we're done. Store the incoming data in a array of Buffers which we concatinate into one buffer at the end. We need to use buffers instead of strings here in order to preserve binary data.

  var chunkBuffers = [];
  var dataLength = 0;
  raw.on("data", function(chunk) {
    chunkBuffers.push(chunk);
    dataLength += chunk.length;
  });
  raw.on("end", function() {
    var body;
    if (typeof Buffer === 'undefined') {

Just concatinate into a string

      body = chunkBuffers.join('');
    } else {

Initialize new buffer and add the chunks one-at-a-time.

      body = new Buffer(dataLength);
      for (var i = 0, pos = 0; i < chunkBuffers.length; i++) {
        chunkBuffers[i].copy(body, pos);
        pos += chunkBuffers[i].length;
      }
    }

    var setBodyAndFinish = function (body) {
      response._body = new Content({ 
      	body: body,
        type: response.getHeader("Content-Type")
      });
      callback(response);
    }

    if (zlib && response.getHeader("Content-Encoding") === 'gzip'){
      zlib.gunzip(body, function (err, gunzippedBody) {
        if (Iconv && response.request.encoding){
          body = Iconv.fromEncoding(gunzippedBody,response.request.encoding);
        } else {
          body = gunzippedBody.toString();
        }
        setBodyAndFinish(body);
      })
    }
    else{
       if (response.request.encoding){
            body = Iconv.fromEncoding(body,response.request.encoding);
        }        
      setBodyAndFinish(body);
    }
  });
};

The Response object can be pretty overwhelming to view using the built-in Node.js inspect method. We want to make it a bit more manageable. This probably goes too far in the other direction.

Response.prototype = {
  inspect: function() {
    var response = this;
    var headers = this.format_headers();
    var summary = ["<Shred Response> ", response.status].join(" ")
    return [ summary, "- Headers:", headers].join("\n");
  },
  format_headers: function () {
    var array = []
    var headers = this.headers
    for (var key in headers) {
      if (headers.hasOwnProperty(key)) {
        var value = headers[key]
        array.push("\t" + key + ": " + value);
      }
    }
    return array.join("\n");
  }
};

Response object properties, all of which are read-only:

Object.defineProperties(Response.prototype, {
  
  • status. The HTTP status code for the response.
  status: {
    get: function() { return this._raw.statusCode; },
    enumerable: true
  },
  • content. The HTTP content entity, if any. Provided as a content object, which will attempt to convert the entity based upon the content-type header. The converted value is available as content.data. The original raw content entity is available as content.body.
  body: {
    get: function() { return this._body; }
  },
  content: {
    get: function() { return this.body; },
    enumerable: true
  },

  headers: {
    get: function() {
      return this._headers;
    },
    enumerable: true
  },
  • isRedirect. Is the response a redirect? These are responses with 3xx status and a Location header.
  isRedirect: {
    get: function() {
      return (this.status>299
          &&this.status<400
          &&this.getHeader("Location"));
    },
    enumerable: true
  },
  • isError. Is the response an error? These are responses with status of 400 or greater.
  isError: {
    get: function() {
      return (this.status === 0 || this.status > 399)
    },
    enumerable: true
  }
});

Add in the getters for accessing the normalized headers.

HeaderMixins.getters(Response);
HeaderMixins.privateSetters(Response);

Work around Mozilla bug #608735 [https://bugzil.la/608735], which causes getAllResponseHeaders() to return {} if the response is a CORS request. xhr.getHeader still works correctly.

var getHeader = Response.prototype.getHeader;
Response.prototype.getHeader = function (name) {
  return (getHeader.call(this,name) ||
    (typeof this._raw.getHeader === 'function' && this._raw.getHeader(name)));
};

module.exports = Response;