Problems with PUT and PATCH requests in production

When we put our first node.js feature into production at about.me, it was pretty exciting. It was fast, stable and everything was working great... except that we were getting persistent reports of problems from a small minority of our users.

We were unable to reproduce the problem, which prompted me to up the detail of our logs. After doing that I noticed that many of the errors were coming from Opera Turbo users. If you're unfamiliar with Opera Turbo, it attempts to speed up your web browsing by sending data through some kind of proxy that presumably compresses it. Requesting data was working just fine, but saving it was causing errors. I tracked it down to the PATCH requests having no Content-Length header so the body-parser express middleware couldn't properly read the request body.

I moved on to other things at that point because a small minority of users on an exotic browser wasn't a priority. As adoption of the feature grew, more reports of saving problems trickled in from non-Opera Turbo users. It was failing for them with any browser they tried. Also, I couldn't find their attempted saves in the node logs. I was baffled.

I found another clue when using a proxy service (Tunnelbear) to use the site from another country and attempting to save some changes. I got the same failure the users had been seeing! A strange fact of this though was that it didn't happen from all countries I proxied from.

I checked the node logs for my own failed saves and didn't see them. Another mysterious occurrence had been reproduced. Then I checked the Nginx logs (we have Nginx acting as a reverse proxy in front of our node servers) and saw that my requests were coming in not as PATCH requests but as METHOD_OTHER.

I tried performing other saves that used POST and they worked fine. Somewhere between these few users and our server, the PUT and PATCH methods were being changed to METHOD_OTHER.

We decided to work around the problem by sticking to the old standbys: GET and POST. We did this by using Backbone's emulateHTTP setting on the frontend and a tiny node middleware on the backend which I'll post in its entirety here.

app.use((req, res, next) => {  
    var overriddenMethod = req.get('X-HTTP-Method-Override');
    if (overriddenMethod) {
        req.method = overriddenMethod;
    }
    next();
});

After that, no more issues.