I was reading through the node API this afternoon, and came across
something that struck me as odd.
There's a race condition in handling HTTP body data. This example is
given in the documentation:
var google = node.http.createClient(80, "google.com");
var request = google.get("/");
request.finish(function (response) {
puts("STATUS: " + response.statusCode);
puts("HEADERS: " + JSON.stringify(response.headers));
response.setBodyEncoding("utf8");
response.addListener("body", function (chunk) {
puts("BODY: " + chunk);
});
});
The race condition is that, if enough time passes before the
response.addListener function gets called, the response object emits
the 'body' event when there are no listeners attached to it. This
modification illustrates the issue:
var google = node.http.createClient(80, 'google.com');
var request = google.get('/');
request.finish(function (resp) {
puts("STATUS: " + resp.statusCode);
puts("HEADERS: " +JSON.stringify(resp.headers));
setTimeout(function () {
resp.setBodyEncoding('utf8');
resp.addListener('body', function(chunk) {
puts("BODY: " + chunk);
});
}, 1000);
});
The 'body' handler never gets called.
You could put the addListener call at the very beginning of the
.finish callback function, but the race is still there...
The immediately obvious solution to this problem would be to have node
buffer the body chunks until resp.receiveBody() is called, at which
point it will emit all the body chunk events it's received. That feels
like a bit of a hack (what happens if that function never gets
called?) but it'd stop my hackles standing up every time I use that
part of the API.
Jeremy
There's a race condition in handling HTTP body data. This example is
given in the documentation:
The body event will not be called until after the "response" callback
(the one passed to finish()) is done. If you delay in setting a
listener, you will simply miss part of the body.
Yes, the documentation needs work. What would be a good way to explain this?
Why should all listeners be added from within the function passed to
finish? I don't think that's even generally the proper approach, let
alone universally.
--Jacob
Oh, sorry, I was confusing myself. Given that the inside of the
`finish` function is the first place we get access to the response
object after it is created, it probably is usually the proper place to
attach callbacks. Nevermind. –Jacob
Thanks. More explanation added in 6f31a37.
node.fs.cat("page.html").addCallback(function (content) {
res.sendHeader(200, {"Content-Type": "text/html"});
res.sendBody(content);
res.finish();
});
So, opening a file (node.fs.open()) doesn't take zero amount of time.
Node might actually have to spin the disk. That means, if you want to
call node.fs.open() on each request, but the HTTP client doesn't wait
for that to happen - they are sending the body now. So you're going to
have to buffer the body until the file is open, and once it's open you
can train the buffer to disk. This sounds hard, but you can be assured
that only one callback is happening at a time, so you don't need to
worry about interfering with other callbacks.
I'd probably use the buffered file object, which is undocumented.
(Sorry, that will be corrected soon.)
include("/file.js");
server = node.http.createServer(function (req, res) {
var f = new File("file.txt", "w+");
req.addListener("body", function (chunk) {
f.write(chunk);
});
req.addListener("complete", function () {
f.close();
});
});