Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I send UpperCase Headers in HTTP

The standard says headers are case insensitive.

Ruby and node both force lower case headers.

We are using an outside server program that expects headers 'AuthToken' to be case sensitive, using .NET framework, and apparently both don't follow standards. We need headers to be up case in this instance.

like image 996
dansch Avatar asked Dec 17 '13 20:12

dansch


3 Answers

At the time of writing, the following setHeader was copied from the _http_outgoing page of node's core lib

var http = require('http');

http.OutgoingMessage.prototype.setHeader = function(name, value) {
  if (arguments.length < 2) {
    throw new Error('`name` and `value` are required for setHeader().');
  }

  if (this._header) {
    throw new Error('Can\'t set headers after they are sent.');
  }

  // NO LOWER CASE
  var key = name//.toLowerCase();
  this._headers = this._headers || {};
  this._headerNames = this._headerNames || {};
  this._headers[key] = value;
  this._headerNames[key] = name;

  // Since we're re-defining the method, we can't use this part anymore
  //if (automaticHeaders[key]) {
  //  this._removedHeader[key] = false;
  //}
};

Commented out part for lowercase

So.. if you get this problem. require http and override this method with the version you're currently using.

It should then work properly. You could do a similar thing of overriding a method in ruby, but it won't be a quick and easy

Then this will work:

require('request')
request({url: 'http://myurl.com', headers: {UpperCaseWorks: 'Yay'}})

EDIT: here's for the newer version of node

OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
  if (this._header) {
    throw new errors.Error('ERR_HTTP_HEADERS_SENT', 'set');
  }
  validateHeader(name, value);

  if (!this[outHeadersKey])
    this[outHeadersKey] = {};
// no more lower case
  const key = name//.toLowerCase();
  this[outHeadersKey][key] = [name, value];

  switch (key.length) {
    case 10:
      if (key === 'connection')
        this._removedConnection = false;
      break;
    case 14:
      if (key === 'content-length')
        this._removedContLen = false;
      break;
    case 17:
      if (key === 'transfer-encoding')
        this._removedTE = false;
      break;
  }
};

Looks like it calls this local method, which'll need to be defined as well

function validateHeader(name, value) {
  let err;
  if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
    err = new errors.TypeError('ERR_INVALID_HTTP_TOKEN', 'Header name', name);
  } else if (value === undefined) {
    err = new errors.TypeError('ERR_HTTP_INVALID_HEADER_VALUE', value, name);
  } else if (checkInvalidHeaderChar(value)) {
    debug('Header "%s" contains invalid characters', name);
    err = new errors.TypeError('ERR_INVALID_CHAR', 'header content', name);
  }
  if (err !== undefined) {
    Error.captureStackTrace(err, validateHeader);
    throw err;
  }
}

And this

const { outHeadersKey } = require('internal/http');

Anyway, check your version of node for what you are overriding

like image 86
dansch Avatar answered Sep 30 '22 08:09

dansch


Piggybacking on Funkodebat's answer, here's my solution for Node 16:

const http = require('http');
// https://github.com/nodejs/node/blob/v16.x/lib/_http_outgoing.js#L574-L587

const { validateHeaderName, validateHeaderValue } = http;

http.OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
  if (this._header) {
    throw new Error('Cannot set headers after they are sent to the client');
  }
  validateHeaderName(name);
  validateHeaderValue(name, value);

  // Extra logic to find kOutHeaders symbol in `this`
  const kOutHeaders = Object.getOwnPropertySymbols(this).find(
    (sym) => sym.toString() === 'Symbol(kOutHeaders)'
  );

  let headers = this[kOutHeaders];
  if (headers === null) this[kOutHeaders] = headers = Object.create(null);

  headers[name] = [name, value]; // toLowerCase removed from here
  return this;
};
like image 36
Charlie Laabs Avatar answered Sep 30 '22 07:09

Charlie Laabs


By looking at the source of NodeJS library on github, you do not need to override the OutgoingMessage.prototype.setHeader

Instead of passing the headers as an Object, you should send them as an Array. Here is a working example :

const http = require('http');

const postData = JSON.stringify({
  'msg': 'Hello World!'
});

const options = {
  hostname: 'www.google.com',
  port: 80,
  path: '/upload',
  method: 'POST',

// use an Array instead of Object to avoid lowercase transformation
  headers: [
    ['Host' ,'localhost' ],
    ['X-CustomHeaderFancy' , 'valueForFancyHeader'],
    ['Content-Type', 'application/json'],
    ['Content-Length', Buffer.byteLength(postData)]
  
  }
};

const req = http.request(options, (res) => {
  console.log(`STATUS: ${res.statusCode}`);
  console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
  res.setEncoding('utf8');
  res.on('data', (chunk) => {
    console.log(`BODY: ${chunk}`);
  });
  res.on('end', () => {
    console.log('No more data in response.');
  });
});

req.on('error', (e) => {
  console.error(`problem with request: ${e.message}`);
});

// Write data to request body
req.write(postData);
req.end();

inside the source code of https://github.com/nodejs/node/blob/v16.x/lib/_http_client.js#L249 there is a test to know if the headers are an array, if it is the case, then it bypass the lowercase transformation. I do not know why it is not documented ? It's a very useful feature.

like image 39
Florian Prud'homme Avatar answered Sep 30 '22 07:09

Florian Prud'homme