Jump To …

headers.js

The header mixins allow you to add HTTP header support to any object. This might seem pointless: why not simply use a hash? The main reason is that, per the HTTP spec, headers are case-insensitive. So, for example, content-type is the same as CONTENT-TYPE which is the same as Content-Type. Since there is no way to overload the index operator in Javascript, using a hash to represent the headers means it's possible to have two conflicting values for a single header.

The solution to this is to provide explicit methods to set or get headers. This also has the benefit of allowing us to introduce additional variations, including snake case, which we automatically convert to what Matthew King has dubbed "corset case" - the hyphen-separated names with initial caps: Content-Type. We use corset-case just in case we're dealing with servers that haven't properly implemented the spec.

var _ = require("underscore")
;

Convert headers to corset-case. Example: CONTENT-TYPE will be converted to Content-Type.

var corsetCase = function(string) {
  return string.toLowerCase()
      .replace("_","-")
      .replace(/(^|-)(\w)/g, 
          function(s) { return s.toUpperCase(); });
};

We suspect that initializeHeaders was once more complicated ...

var initializeHeaders = function(object) {
  return {};
};

Access the _headers property using lazy initialization. Warning: If you mix this into an object that is using the _headers property already, you're going to have trouble.

var $H = function(object) {
  return object._headers||(object._headers=initializeHeaders(object));
};

Hide the implementations as private functions, separate from how we expose them.

The "real" getHeader function: get the header after normalizing the name.

var getHeader = function(object,name) {
  return $H(object)[corsetCase(name)];
};

The "real" getHeader function: get one or more headers, or all of them if you don't ask for any specifics.

var getHeaders = function(object,names) {
  var keys = (names && names.length>0) ? names : Object.keys($H(object));
  var hash = keys.reduce(function(hash,key) {
    hash[key] = getHeader(object,key);
    return hash;
  },{});

Freeze the resulting hash so you don't mistakenly think you're modifying the real headers.

  Object.freeze(hash);
  return hash;
};

The "real" setHeader function: set a header, after normalizing the name.

var setHeader = function(object,name,value) {
  $H(object)[corsetCase(name)] = value;
  return object;
};

The "real" setHeaders function: set multiple headers based on a hash.

var setHeaders = function(object,hash) {
  for( var key in hash ) { setHeader(object,key,hash[key]); };
  return this;
};

Here's where we actually bind the functionality to an object. These mixins work by exposing mixin functions. Each function mixes in a specific batch of features.

module.exports = {
  

Add getters.

  getters: function(constructor) {
    constructor.prototype.getHeader = function(name) { return getHeader(this,name); };
    constructor.prototype.getHeaders = function() { return getHeaders(this,_(arguments)); };
  },

Add setters but as "private" methods.

  privateSetters: function(constructor) {
    constructor.prototype._setHeader = function(key,value) { return setHeader(this,key,value); };
    constructor.prototype._setHeaders = function(hash) { return setHeaders(this,hash); };
  },

Add setters.

  setters: function(constructor) {
    constructor.prototype.setHeader = function(key,value) { return setHeader(this,key,value); };
    constructor.prototype.setHeaders = function(hash) { return setHeaders(this,hash); };
  },

Add both getters and setters.

  gettersAndSetters: function(constructor) {
    constructor.prototype.getHeader = function(name) { return getHeader(this,name); };
    constructor.prototype.getHeaders = function() { return getHeaders(this,_(arguments)); };
    constructor.prototype.setHeader = function(key,value) { return setHeader(this,key,value); };
    constructor.prototype.setHeaders = function(hash) { return setHeaders(this,hash); };
  },
};