How to cancel HTTP upload from data events?

The proper, spec-compliant thing to do here is simply send a HTTP 413 response early – that is, as soon as you detect that the client has sent more bytes than you want to handle. It is up to you whether or not you terminate the socket after sending the error response. This is in line with RFC 2616: (emphasis added)

413 Request Entity Too Large

The server is refusing to process a request because the request entity is larger than the server is willing or able to process. The server MAY close the connection to prevent the client from continuing the request.

What happens next is not ideal.

  • If you leave the socket open, all browsers (Chrome 30, IE 10, Firefox 21) will keep sending data until the entire file is uploaded. Then and only then, the browser will display your error message. This really sucks since the user must wait for the entire file to complete the upload, only to find out the server rejected it. It also wastes your bandwidth.

    The browsers’ current behavior is in violation of RFC 2616 § 8.2.2:

    An HTTP/1.1 (or later) client sending a message-body SHOULD monitor the network connection for an error status while it is transmitting the request. If the client sees an error status, it SHOULD immediately cease transmitting the body. If the body is being sent using a “chunked” encoding (section 3.6), a zero length chunk and empty trailer MAY be used to prematurely mark the end of the message. If the body was preceded by a Content-Length header, the client MUST close the connection.

    There are open Chrome and Firefox issues, but don’t expect a fix any time soon.

  • If you close the socket immediately after sending the HTTP 413 response, all browsers will obviously stop uploading immediately, but they most currently show a “connection reset” error (or similar), not any HTML you might send in the response.

    Again, this is probably a violation of the spec (which allows the server to send a response early and close the connection), but I wouldn’t expect browser fixes any time soon here either.

    Update: As of 4/15, Chrome may display your 413 HTML when you close the connection early. This only works when the browser is running on Linux and Mac OS X. On Windows, Chrome still displays ERR_CONNECTION_RESET network error rather than the HTML you sent. (IE 11 and Firefox 37 continue to just show a network error on all platforms.)

So your choices with traditional plain HTTP uploads are:

  • Show a friendly error message, but only after the upload runs to completion. This wastes time and bandwidth.

  • Fail fast, but leave users confused with a cryptic browser error screen.

Your best bet here is probably to use a AJAX uploader where you have more control over the user experience. You should still provide a tradtional upload form as a fallback, and I’d use the “fail fast” option (close the socket) to prevent wasted time and bandwidth.

Here’s some example code that kills a request if it receives more than 1 kB. I’m using Express, but the same should apply with node’s vanilla HTTP library.

Note: In reality, you should use formidable multiparty to process your uploads (it’s what Connect/Express uses), and it has its own way to monitor upload data.

var express = require("express")
    , app = express();

app.get("https://stackoverflow.com/", function(req, res) {
    res.send('Uploads &gt; 1 kB rejected<form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit"></form>');
});

app.post('/upload', function(req, res) {
    var size = 0;

    var gotData = function(d) {
        size += d.length; // add this chunk's size to the total number of bytes received thus far
        console.log('upload chunk', size);
        if (size > 1024) {
            console.log('aborting request');
            req.removeListener('data', gotData); // we need to remove the event listeners so that we don't end up here more than once
            req.removeListener('end', reqEnd);
            res.header('Connection', 'close'); // with the Connection: close header set, node will automatically close the socket...
            res.send(413, 'Upload too large'); // ... after sending a response
        }
    };

    var reqEnd = function() {
       res.send('ok, got ' + size + ' bytes');
    }

    req.on('data', gotData);

    req.on('end', reqEnd);
});

app.listen(3003);

Leave a Comment