8

I would like to write an HTTP server that answer to request using a non-standard HTTP method (verb). For instance, the client would make a request like FOO / HTTP/.1.1. And on the server side, this request would be handled by something like:

var express = require('express');

var app = express.createServer();

app.configure(function(){
  app.use(express.logger({ format: ':method :url' }));
  app.use(express.methodOverride());
});

app.foo('/', function(req, res){
    res.send('Hello World');
});

app.listen(3000);

I appended my non-standard method to the array exported in ExpressJS's lib/router/methods.js. This allow me to write my server code as expected. When using express.methodOverride() and a POST request with _method=foo, it works. But an actual FOO request doesn't work. As soon as the client send the first line of the request the connection is closed by the server:

$telnet localhost 3000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
FOO / HTTP/1.1
Connection closed by foreign host.

I would like to be able to implement this with ExpressJS and without avoid hacking into its core file.

Any idea if this is possible and how?

Pierre Buyle
  • 4,883
  • 2
  • 32
  • 31

4 Answers4

8

Short answer: No, it's not possible. Not without implementing your own HTTP module.

To test, start a barebones HTTP server ...

$ node
> require('http').createServer(function(req, res) {
...   console.log(req.method);
...   res.end();
... }).listen(8080);

Then (as you've already done) telnet to it and issue a GET and FOO request ...

$ telnet localhost 8080
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1

HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked

0

FOO / HTTP/1.1
Connection closed by foreign host.

$ 

In node console you'll see

GET

... but no FOO. So, node's native HTTP module, which Express uses, does not make these requests available.

broofa
  • 37,461
  • 11
  • 73
  • 73
  • 3
    Just to post an update - in the current node (4.6) and express (4) versions, you can handle custom HTTP verbs. Listening to `app.all` and then checking `req.method` in the callback works. – MatsLindh Oct 23 '16 at 13:18
  • @MatsLindh, are you sure? Trying code from this answer on node 10.11.0 and it responds 400 if method is unknown for node. Or I need to specify some additional flags? – Qwertiy Sep 18 '19 at 11:18
  • @Qwertiy Ask a new question if you have any issues and add the relevant code. It's impossible to say three years later without information about what you've tried. – MatsLindh Sep 18 '19 at 12:07
4

Node has a hard-coded whitelist of acceptable HTTP verbs in C.

In order to accept custom verbs, you must modify the HTTP parser and recompile node.


You mentioned that you're trying to implement PURGE, which was added to the whitelist in v0.7.5.

Community
  • 1
  • 1
josh3736
  • 139,160
  • 33
  • 216
  • 263
1

As others have said, Node.js' HTTP server library is configured to accept only specific verbs. Ben Noordius' suggestion of using Parsley doesn't work either, since that library accepts an even smaller whitelist of verbs. (It also hasn't been maintained in quite some time.)

At this stage, if we want to support oddball requests, we have to take more drastic measures. Here's a nice ugly hack for you that involves duck punching some internal behavior. This works on v0.10.x of Node.js, but test carefully on newer versions as they become available.

In my case, I needed to support not only a non-standard verb, but a non-standard protocol version identifier as well, and a missing Content-Length header for Icecast source streams:

SOURCE /live ICE/1.0

The following should get you started:

server.on('connection', function (socket) {
    var originalOnDataFunction = socket.ondata;
    var newLineOffset;
    var receiveBuffer = new Buffer(0);
    socket.ondata = function (d, start, end) {
        receiveBuffer = Buffer.concat([receiveBuffer, d.slice(start, end)]);
        if ((newLineOffset = receiveBuffer.toString('ascii').indexOf('\n')) > -1) {
            var firstLineParts = receiveBuffer.slice(0, newLineOffset).toString().split(' ');
            firstLineParts[0] = firstLineParts[0].replace(/^SOURCE$/ig, 'PUT');
            firstLineParts[2] = firstLineParts[2].replace(/^ICE\//ig, 'HTTP/');
            receiveBuffer = Buffer.concat([
                new Buffer(
                    firstLineParts.join(' ') + '\r\n' + 
                    'Content-Length: 9007199254740992\r\n'
                ), 
                receiveBuffer.slice(newLineOffset +1)
            ]);

            socket.ondata = originalOnDataFunction;
            socket.ondata.apply(this, [receiveBuffer, 0, receiveBuffer.length]);
        }
    };
}

It's ugly, but works. I'm not particularly happy about it, but when choosing between a rough built-from-the-ground-up HTTP parser or tweaking an existing one, I choose to tweak in this instance.

Brad
  • 159,648
  • 54
  • 349
  • 530
1

For anyone who needs it, there is http-parser-js, which replaces Node's built-in HTTP parser.

Their README contains an example of monkey-patching the parser, though I find that it wasn't enough, as both the http-parser-js and the http modules have a hardcoded list of methods.

So, you have to replace the parser and edit the list of methods:

const { HTTPParser } = require('http-parser-js');
HTTPParser.methods.push('FOOBAR');

const binding = process.binding('http_parser');
binding.HTTPParser = HTTPParser;
binding.methods = HTTPParser.methods;
require('http').METHODS = HTTPParser.methods;

Later Node versions may not support process.binding, in which case, you can use the --expose-internals flag for Node (see this issue):

const { internalBinding } = require('internal/test/binding');
const binding = internalBinding('http_parser');

From the looks of it, the http2 module's parser accepts any method, in case that's an option. See this issue about invalid HTTP methods. Unfortunately, express and the likes do not use http2.


And for anyone who was in my shoes, proxying requests to a legacy server in Create React App, use the above snippet in webpack-dev-server, at the top of Server.js, in order to monkey-patch the parser. Hopefully everything switches to http2 soon...

comp
  • 106
  • 1
  • 4