Testing http range requests in node, when we stream from
fs.createReadStream('index.html').pipe(res)
to a http res
of a http server, the http client from node accepts it.
But, when I pipe a Stream of a Buffer, with:
const content = fs.readFileSync("src/index.html");
const stream = new Readable();
stream.push(
opts.start && opts.end
? content.slice(opts.start, opts.end + 1)
: content
);
stream.push(null);
stream.pipe(res);
Curl and browsers accept it, except NodeJS http
client, that throws:
events.js:180
throw er; // Unhandled 'error' event
^
Error: Parse Error
at Socket.socketOnData (_http_client.js:452:22)
at Socket.emit (events.js:203:13)
at addChunk (_stream_readable.js:294:12)
at readableAddChunk (_stream_readable.js:275:11)
at Socket.Readable.push (_stream_readable.js:210:10)
at TCP.onStreamRead (internal/stream_base_commons.js:166:17)
Emitted 'error' event at:
at Socket.socketOnData (_http_client.js:458:9)
at Socket.emit (events.js:203:13)
[... lines matching original stack trace ...]
at TCP.onStreamRead (internal/stream_base_commons.js:166:17) {
bytesParsed: 234,
code: 'HPE_INVALID_CONSTANT',
reason: 'Expected HTTP/'
}
To test it, just change the line 56 from 'read'
to 'buffer'
:
index.js
const http = require("http");
const fs = require("fs");
const Readable = require("stream").Readable;
const stats = fs.statSync("index.html");
const handler = function(read_or_buffer) {
return function(req, res) {
let code = 200;
const opts = {};
const headers = {
"Content-Length": stats.size,
"Content-Type": "text/html",
"Last-Modified": stats.mtime.toUTCString()
};
if (req.headers.range) {
code = 206;
let [x, y] = req.headers.range.replace("bytes=", "").split("-");
let end = (opts.end = parseInt(y, 10) || stats.size - 1);
let start = (opts.start = parseInt(x, 10) || 0);
if (start >= stats.size || end >= stats.size) {
res.setHeader("Content-Range", `bytes */${stats.size}`);
res.statusCode = 416;
return res.end();
}
headers["Content-Range"] = `bytes ${start}-${end}/${stats.size}`;
headers["Content-Length"] = end - start + 1;
headers["Accept-Ranges"] = "bytes";
}
res.writeHead(code, headers);
if (read_or_buffer == "read")
fs.createReadStream("index.html", opts).pipe(res);
if (read_or_buffer == "buffer") {
const content = fs.readFileSync("index.html");
const stream = new Readable();
stream.push(
opts.start && opts.end
? content.slice(opts.start, opts.end + 1)
: content
);
stream.push(null);
stream.pipe(res);
}
};
};
http.createServer(handler("read")).listen(8080);
// TESTS
const options = { headers: { Range: "bytes=0-4" } };
http.get("http://127.0.0.1:8080/", options, response => {
let data = "";
response.on("data", chunk => (data += chunk));
response.on("end", () => {
console.log(data);
process.exit();
});
});
index.html
Hello world!
When the server receive a http range request starting from 0
:
opts.start && opts.end
evaluates to false because opts.start
is 0
so the code was sending the whole buffer and not the expected slice.
As NodeJS strictly meets the HTTP specification, it was not accepting the request.
The solution was to verify, when opts.start
exists, if it is zero
:
opts.start || opts.start === 0 && opts.end