Skip to content

Instantly share code, notes, and snippets.

@brianloveswords
Last active November 1, 2016 04:55
Show Gist options
  • Save brianloveswords/0044b0b57b672b302f2d to your computer and use it in GitHub Desktop.
Save brianloveswords/0044b0b57b672b302f2d to your computer and use it in GitHub Desktop.
/* Problem: Notifying clients of an error after headers have been sent
In normal HTTP buffer-everything-then-send, you would be able to
set an error status code, either 4xx or 5xx, to let the client know
that something has gone awry.
However when using a streaming interface the headers, including the
status code, are sent before it is known that the message will
finish sending successfully. With streams, the header can't be
trusted to determine the success/failure state of the message.
Potential Solution: HTTP 1.1 Trailers
It seems the folks who spec'd out HTTP 1.1 thought of this:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
When using chunked transfer encoding, it is possible to send along
a "trailer" after the body has been sent. By adding a
`Content-SHA256` trailer with the computed hash of the final value,
a client can check the integrity of the message upon receiving te
final chunk.
Caveats:
There are no common HTTP trailers like there are HTTP headers so
there won't be out-of-the-box support for any specific
trailer. Also, since there are no none semantics for trailers, if
the user-agent is a browser, failures will be undetectable.
Open Question:
Is there a better way to signal an error to the client other than
using trailers and hoping the client performs the integrity check?
Maybe something that would also work in the browser?
*/
var fs = require('fs');
var util = require('util');
var http = require('http');
var crypto = require('crypto');
var assert = require('assert');
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain',
'Trailer': 'Content-SHA256',
});
var shasum = crypto.createHash('sha256');
var dataStream = fs.createReadStream('data.txt');
dataStream.pipe(shasum, {end: false});
dataStream.pipe(res, {end: false});
dataStream.on('end', function () {
res.addTrailers({'Content-SHA256': shasum.digest('hex')});
res.end();
});
dataStream.on('error', function () {
// Set to an invalid value
res.addTrailers({'Content-SHA256': "Stream Error"});
res.end();
});
}).listen(3000, function () {
var done = this.close.bind(this);
// Example API consumer
var shasum = crypto.createHash('sha256');
http.get('http://localhost:3000', function (res) {
res.pipe(shasum, {end: false});
res.on('end', function () {
// Client can validate message integrity by calculating the
// message hash and comparing it to the given hash in the trailer.
var givenHash = res.trailers['content-sha256'];
var calulatedHash = shasum.digest('hex');
assert.equal(givenHash, calulatedHash);
console.log('Okay');
done();
});
});
});
@mafintosh
Copy link

In your error handler

dataStream.on('error', function () {
  // Set to an invalid value
  res.addTrailers({'Content-SHA256': "Stream Error"});
  res.end();
});

You could call res.destroy() instead.

dataStream.on('error', function () {
  res.destroy();
});

This would trigger req.on('close', ... in your client instead since it wouldn't receive \r\n\r\n which .end() sends as a end-of-data signal.

var req = http.get('http://localhost:3000', function (res) {
  res.on('end', function () {
    console.log('end-of-data all is good')
  })
})

req.on('close', function() {
  console.log('not so good')
})

This doesn't give you as fine tuned error handling as status codes but allows you to signal to the client that something wasn't right. I'm betting the browser would even show the request as aborted as well (I need to test this though)

@brianloveswords
Copy link
Author

Unfortunately the browser does not show the request as aborted. It also still triggers the end event on the request's response! It does trigger the request's close in addition, though.

@jedwards1211
Copy link

So if we're using streaming servers we're basically too far ahead of the curve right now?

@jedwards1211
Copy link

jedwards1211 commented Nov 1, 2016

@mafintosh unfortunately chrome doesn't show an error in this case, it shows a blank page. The only indication is that in the console it prints net::ERR_INCOMPLETE_CHUNKED_ENCODING.
curl does show an error message though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment