I need to implement an HTTP server in Node.JS, using the built-in http module, which will serve a big file from the filesystem. However, I have encountered a problem, my solution works only when used directly. It doesn't work when it is put behind Nginx Reverse Proxy.
My current implementation (example):
let http = require('http');
let fs = require('fs');
http.createServer(function (req, res) {
let size;
size = fs.statSync("./some/file").size;
res.setHeader("Content-Type","application/octet-stream");
res.setHeader("Content-Length", `${size}`);
res.setHeader("Content-Disposition", "attachment; filename='some.filename'");
res.flushHeaders();
let seek = 0;
let buf;
let bs = 4096;
let fd;
fd = fs.openSync("./some/file", "r");
while (seek < size) {
buf = Buffer.alloc(bs);
fs.readSync(fd,buf,null,bs,seek);
res.write(buf);
seek = seek + bs
}
res.end();
}).listen(8080);
It works well when used locally. But it doesn't work behind a reverse proxy. The browser shows an error, and in curl I see:
HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR (err 1)
I'm using nginx as the reverse proxy, and it is configured to only use http1.1 when talking to Upstream. There is no issues if I implement it like this:
...
res.end(fs.readFileSync("./some/file"));
...
But it is obviously not possible for files as big as few hundreds GBs. There is no XY problem here. I know that there are tools for serving files. And I know that nginx can do it. But I'm interested in fixing my implementation. Since it fails to work only when put behind a reverse proxy, I think that it's somehow not compliant with HTTP protocol. But I couldn't figure it out myself. Thanks!
I was expecting the file transfer to work correctly both directly and behind Nginx.
The issue here is obvious, took me some time to realize though.
Body length doesn't match the one advertised by the
Content-LengthheaderWhen we
readinto a <blocksize> bytes buffer, obviously the buffer remains <blocksize> bytes long, regardless of the size of data we read. The solution above is already bad because any file gets aligned (padded) to a multiple of <blocksize>, but what is the fatal issue with it, is the incorrectContent-Length. Because, given the fact that any file is padded to a multiple of <blocksize>, setting the discussed header's value to the actual size of file just means setting it wrong (until it is actually a multiple of <blocksize>, which will rarely happen in practice).Some HTTP implementations are made with at least minimal compatibility for minor violations of HTTP standard. But sending more/less bytes than set in
Content-Lengthis too much. When working with the browser directly, some browsers will try to show the response anyway, despite invalid length (or even worse things). But when using Nginx, or other reverse proxy, it is absolutely correct behavior for such response to cause an error.