0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-29 15:06:33 +01:00

Remove error codes from file on_completion callbacks. Use file.onError.

The error codes still remain for the two general file system operations:
rename and stat.

Additionally I've removed the actionQueue for file system operations. They
are sent directly into the thread pool.
This commit is contained in:
Ryan 2009-05-25 13:17:35 +02:00
parent 58c13e5192
commit 5c2389fada
4 changed files with 239 additions and 166 deletions

View File

@ -12,55 +12,40 @@
using namespace v8;
using namespace node;
#define FD_SYMBOL v8::String::NewSymbol("fd")
#define ACTION_QUEUE_SYMBOL v8::String::NewSymbol("_actionQueue")
#define ENCODING_SYMBOL v8::String::NewSymbol("encoding")
#define FD_SYMBOL String::NewSymbol("fd")
#define ACTION_QUEUE_SYMBOL String::NewSymbol("_actionQueue")
#define ENCODING_SYMBOL String::NewSymbol("encoding")
#define CALLBACK_SYMBOL String::NewSymbol("callbaccallback")
#define POLL_ACTIONS_SYMBOL String::NewSymbol("_pollActions")
#define UTF8_SYMBOL v8::String::NewSymbol("utf8")
#define RAW_SYMBOL v8::String::NewSymbol("raw")
static void
InitActionQueue (Handle<Object> handle)
{
handle->Set(ACTION_QUEUE_SYMBOL, Array::New());
}
// This is the file system object which contains methods
// for accessing the file system (like rename, mkdir, etC).
// In javascript it is called "File".
static Persistent<Object> fs;
#define UTF8_SYMBOL String::NewSymbol("utf8")
#define RAW_SYMBOL String::NewSymbol("raw")
void
File::Initialize (Handle<Object> target)
{
if (!fs.IsEmpty())
return;
HandleScope scope;
fs = Persistent<Object>::New(target);
InitActionQueue(fs);
// file system methods
NODE_SET_METHOD(fs, "_ffi_rename", FileSystem::Rename);
NODE_SET_METHOD(fs, "_ffi_stat", FileSystem::Stat);
NODE_SET_METHOD(fs, "strerror", FileSystem::StrError);
NODE_SET_METHOD(target, "rename", FileSystem::Rename);
NODE_SET_METHOD(target, "stat", FileSystem::Stat);
NODE_SET_METHOD(target, "strerror", FileSystem::StrError);
fs->Set(String::NewSymbol("STDIN_FILENO"), Integer::New(STDIN_FILENO));
fs->Set(String::NewSymbol("STDOUT_FILENO"), Integer::New(STDOUT_FILENO));
fs->Set(String::NewSymbol("STDERR_FILENO"), Integer::New(STDERR_FILENO));
target->Set(String::NewSymbol("STDIN_FILENO"), Integer::New(STDIN_FILENO));
target->Set(String::NewSymbol("STDOUT_FILENO"), Integer::New(STDOUT_FILENO));
target->Set(String::NewSymbol("STDERR_FILENO"), Integer::New(STDERR_FILENO));
Local<FunctionTemplate> file_template = FunctionTemplate::New(File::New);
file_template->InstanceTemplate()->SetInternalFieldCount(1);
// file methods
NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_open", File::Open);
NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_close", File::Close);
NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_write", File::Write);
NODE_SET_PROTOTYPE_METHOD(file_template, "_ffi_read", File::Read);
file_template->InstanceTemplate()->SetAccessor(ENCODING_SYMBOL, File::GetEncoding, File::SetEncoding);
fs->Set(String::NewSymbol("File"), file_template->GetFunction());
target->Set(String::NewSymbol("File"), file_template->GetFunction());
}
Handle<Value>
@ -95,32 +80,15 @@ CallTopCallback (Handle<Object> handle, const int argc, Handle<Value> argv[])
{
HandleScope scope;
Local<Value> queue_value = handle->Get(ACTION_QUEUE_SYMBOL);
assert(queue_value->IsArray());
Local<Array> queue = Local<Array>::Cast(queue_value);
Local<Value> top_value = queue->Get(Integer::New(0));
if (top_value->IsObject()) {
Local<Object> top = top_value->ToObject();
Local<Value> callback_value = top->Get(String::NewSymbol("callback"));
if (callback_value->IsFunction()) {
Handle<Function> callback = Handle<Function>::Cast(callback_value);
TryCatch try_catch;
callback->Call(handle, argc, argv);
if(try_catch.HasCaught()) {
node::fatal_exception(try_catch);
return;
}
}
}
// poll_actions
Local<Value> poll_actions_value = handle->Get(String::NewSymbol("_pollActions"));
Local<Value> poll_actions_value = handle->Get(POLL_ACTIONS_SYMBOL);
assert(poll_actions_value->IsFunction());
Handle<Function> poll_actions = Handle<Function>::Cast(poll_actions_value);
poll_actions->Call(handle, 0, NULL);
TryCatch try_catch;
poll_actions->Call(handle, argc, argv);
if(try_catch.HasCaught())
node::fatal_exception(try_catch);
}
Handle<Value>
@ -134,6 +102,13 @@ FileSystem::Rename (const Arguments& args)
String::Utf8Value path(args[0]->ToString());
String::Utf8Value new_path(args[1]->ToString());
Persistent<Function> *callback = NULL;
if (args[2]->IsFunction()) {
Local<Function> l = Local<Function>::Cast(args[2]);
callback = new Persistent<Function>();
*callback = Persistent<Function>::New(l);
}
node::eio_warmup();
eio_rename(*path, *new_path, EIO_PRI_DEFAULT, AfterRename, NULL);
@ -147,7 +122,18 @@ FileSystem::AfterRename (eio_req *req)
const int argc = 1;
Local<Value> argv[argc];
argv[0] = Integer::New(req->errorno);
CallTopCallback(fs, argc, argv);
if (req->data) {
Persistent<Function> *callback = reinterpret_cast<Persistent<Function>*>(req->data);
TryCatch try_catch;
(*callback)->Call(Context::GetCurrent()->Global(), argc, argv);
if(try_catch.HasCaught())
node::fatal_exception(try_catch);
free(callback);
}
return 0;
}
@ -161,8 +147,15 @@ FileSystem::Stat (const Arguments& args)
String::Utf8Value path(args[0]->ToString());
Persistent<Function> *callback = NULL;
if (args[1]->IsFunction()) {
Local<Function> l = Local<Function>::Cast(args[1]);
callback = new Persistent<Function>();
*callback = Persistent<Function>::New(l);
}
node::eio_warmup();
eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, NULL);
eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, callback);
return Undefined();
}
@ -180,7 +173,7 @@ FileSystem::AfterStat (eio_req *req)
argv[1] = stats;
if (req->result == 0) {
struct stat *s = static_cast<struct stat*>(req->ptr2);
struct stat *s = reinterpret_cast<struct stat*>(req->ptr2);
/* ID of device containing file */
stats->Set(NODE_SYMBOL("dev"), Integer::New(s->st_dev));
@ -210,8 +203,17 @@ FileSystem::AfterStat (eio_req *req)
stats->Set(NODE_SYMBOL("ctime"), Date::New(1000*static_cast<double>(s->st_ctime)));
}
CallTopCallback(fs, argc, argv);
if (req->data) {
Persistent<Function> *callback = reinterpret_cast<Persistent<Function>*>(req->data);
TryCatch try_catch;
(*callback)->Call(Context::GetCurrent()->Global(), argc, argv);
if(try_catch.HasCaught())
node::fatal_exception(try_catch);
free(callback);
}
return 0;
}
@ -237,7 +239,7 @@ File::File (Handle<Object> handle)
{
HandleScope scope;
encoding_ = RAW;
InitActionQueue(handle);
handle->Set(ACTION_QUEUE_SYMBOL, Array::New());
}
File::~File ()
@ -277,7 +279,7 @@ File::Close (const Arguments& args)
int
File::AfterClose (eio_req *req)
{
File *file = static_cast<File*>(req->data);
File *file = reinterpret_cast<File*>(req->data);
if (req->result == 0) {
file->handle_->Delete(FD_SYMBOL);
@ -338,7 +340,7 @@ File::Open (const Arguments& args)
int
File::AfterOpen (eio_req *req)
{
File *file = static_cast<File*>(req->data);
File *file = reinterpret_cast<File*>(req->data);
HandleScope scope;
if(req->result >= 0) {
@ -364,6 +366,8 @@ File::Write (const Arguments& args)
File *file = NODE_UNWRAP(File, args.Holder());
off_t pos = args[1]->IntegerValue();
char *buf = NULL;
size_t length = 0;
@ -371,14 +375,14 @@ File::Write (const Arguments& args)
// utf8 encoding
Local<String> string = args[0]->ToString();
length = string->Utf8Length();
buf = static_cast<char*>(malloc(length));
buf = reinterpret_cast<char*>(malloc(length));
string->WriteUtf8(buf, length);
} else if (args[0]->IsArray()) {
// raw encoding
Local<Array> array = Local<Array>::Cast(args[0]);
length = array->Length();
buf = static_cast<char*>(malloc(length));
buf = reinterpret_cast<char*>(malloc(length));
for (unsigned int i = 0; i < length; i++) {
Local<Value> int_value = array->Get(Integer::New(i));
buf[i] = int_value->Int32Value();
@ -389,8 +393,6 @@ File::Write (const Arguments& args)
return Undefined();
}
off_t pos = args[1]->IntegerValue();
if (file->handle_->Has(FD_SYMBOL) == false) {
printf("trying to write to a bad fd!\n");
return Undefined();
@ -408,9 +410,9 @@ File::Write (const Arguments& args)
int
File::AfterWrite (eio_req *req)
{
File *file = static_cast<File*>(req->data);
File *file = reinterpret_cast<File*>(req->data);
//char *buf = static_cast<char*>(req->ptr2);
//char *buf = reinterpret_cast<char*>(req->ptr2);
free(req->ptr2);
ssize_t written = req->result;
@ -429,20 +431,22 @@ File::AfterWrite (eio_req *req)
Handle<Value>
File::Read (const Arguments& args)
{
if (args.Length() < 1) return Undefined();
if (!args[0]->IsNumber()) return Undefined();
if (!args[1]->IsNumber()) return Undefined();
HandleScope scope;
if (args.Length() < 1 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
return ThrowException(String::New("Bad parameter for _ffi_read()"));
}
File *file = NODE_UNWRAP(File, args.Holder());
size_t length = args[0]->IntegerValue();
off_t pos = args[1]->IntegerValue();
size_t len = args[0]->IntegerValue();
off_t pos = args[1]->IntegerValue();
int fd = file->GetFD();
assert(fd >= 0);
// NOTE: NULL pointer tells eio to allocate it itself
// NOTE: 2nd param: NULL pointer tells eio to allocate it itself
node::eio_warmup();
eio_read(fd, NULL, length, pos, EIO_PRI_DEFAULT, File::AfterRead, file);
eio_read(fd, NULL, len, pos, EIO_PRI_DEFAULT, File::AfterRead, file);
file->Attach();
return Undefined();
@ -451,32 +455,33 @@ File::Read (const Arguments& args)
int
File::AfterRead (eio_req *req)
{
File *file = static_cast<File*>(req->data);
File *file = reinterpret_cast<File*>(req->data);
HandleScope scope;
const int argc = 2;
Local<Value> argv[argc];
argv[0] = Integer::New(req->errorno);
char *buf = static_cast<char*>(req->ptr2);
char *buf = reinterpret_cast<char*>(req->ptr2);
if(req->result == 0) {
if (req->result == 0) {
// eof
argv[1] = Local<Value>::New(Null());
} else {
size_t length = req->result;
size_t len = req->result;
if (file->encoding_ == UTF8) {
// utf8 encoding
argv[1] = String::New(buf, req->result);
} else {
// raw encoding
Local<Array> array = Array::New(length);
for (unsigned int i = 0; i < length; i++) {
Local<Array> array = Array::New(len);
for (unsigned int i = 0; i < len; i++) {
array->Set(Integer::New(i), Integer::New(buf[i]));
}
argv[1] = array;
}
}
CallTopCallback(file->handle_, argc, argv);
file->Detach();

View File

@ -1,13 +1,5 @@
node.fs.rename = function (file1, file2, callback) {
this._addAction("rename", [file1, file2], callback);
};
node.fs.stat = function (path, callback) {
this._addAction("stat", [path], callback);
};
node.fs.exists = function (path, callback) {
this._addAction("stat", [path], function (status) {
node.fs.stat(path, function (status) {
callback(status == 0);
});
}
@ -16,16 +8,17 @@ node.fs.cat = function (path, encoding, callback) {
var file = new node.fs.File();
file.encoding = encoding;
var content = "";
if (file.encoding == "raw") content = [];
file.onError = function (method, errno, msg) {
callback(-1);
};
var content = (file.encoding == "raw" ? "" : []);
var pos = 0;
var chunkSize = 10*1024;
function readChunk () {
file.read(chunkSize, pos, function (status, chunk) {
file.read(chunkSize, pos, function (chunk) {
if (chunk) {
if (chunk.constructor == String)
content += chunk;
else
@ -40,22 +33,11 @@ node.fs.cat = function (path, encoding, callback) {
});
}
file.open(path, "r", function (status) {
if (status == 0)
readChunk();
else
callback (status);
file.open(path, "r", function () {
readChunk();
});
}
node.fs.File.prototype.puts = function (data, callback) {
this.write(data + "\n", -1, callback);
};
node.fs.File.prototype.print = function (data, callback) {
this.write(data, -1, callback);
};
node.fs.File.prototype.open = function (path, mode, callback) {
this._addAction("open", [path, mode], callback);
};
@ -64,12 +46,20 @@ node.fs.File.prototype.close = function (callback) {
this._addAction("close", [], callback);
};
node.fs.File.prototype.read = function (length, pos, callback) {
this._addAction("read", [length, pos], callback);
};
node.fs.File.prototype.write = function (buf, pos, callback) {
this._addAction("write", [buf, pos], callback);
};
node.fs.File.prototype.read = function (length, pos, callback) {
this._addAction("read", [length, pos], callback);
node.fs.File.prototype.print = function (data, callback) {
this.write(data, -1, callback);
};
node.fs.File.prototype.puts = function (data, callback) {
this.write(data + "\n", -1, callback);
};
// Some explanation of the File binding.
@ -84,38 +74,65 @@ node.fs.File.prototype.read = function (length, pos, callback) {
// the member _actionQueue = []
//
// Any of the methods called on a file are put into this queue. When they
// reach the head of the queue they will be executed. C++ calles the
// reach the head of the queue they will be executed. C++ calls the
// method _pollActions each time it becomes idle. If there is no action
// currently being executed then _pollActions will not be called. Thus when
// actions are added to an empty _actionQueue, they should be immediately
// currently being executed then _pollActions will not be called. When
// actions are added to an empty _actionQueue, they will be immediately
// executed.
//
// When an action has completed, the C++ side is going to look at the first
// When an action has completed, the C++ side is looks at the first
// element of _actionQueue in order to get a handle on the callback
// function. Only after that completion callback has been made can the
// action be shifted out of the queue.
//
// See File::CallTopCallback() in file.cc to see the other side of the
// binding.
node.fs._addAction = node.fs.File.prototype._addAction = function (method, args, callback) {
this._actionQueue.push({ method: method
, callback: callback
, args: args
});
if (this._actionQueue.length == 1) this._act();
}
// See File::CallTopCallback() in file.cc for the other side of the binding.
node.fs._act = node.fs.File.prototype._act = function () {
node.fs.File.prototype._addAction = function (method, args, callback) {
// This adds a method to the queue.
var action = { method: method
, callback: callback
, args: args
};
this._actionQueue.push(action);
// If the queue was empty, immediately call the method.
if (this._actionQueue.length == 1) this._act();
};
node.fs.File.prototype._act = function () {
// peek at the head of the queue
var action = this._actionQueue[0];
if (action)
// TODO FIXME what if the action throws an error?
if (action) {
// execute the c++ version of the method. the c++ version
// is gotten by appending "_ffi_" to the method name.
this["_ffi_" + action.method].apply(this, action.args);
}
};
// called from C++ after each action finishes
// (i.e. when it returns from the thread pool)
node.fs._pollActions = node.fs.File.prototype._pollActions = function () {
node.fs.File.prototype._pollActions = function () {
var action = this._actionQueue[0];
var errno = arguments[0];
if (errno < 0) {
if (this.onError)
this.onError(action.method, errno, node.fs.strerror(errno));
this._actionQueue = []; // empty the queue.
return;
}
var rest = [];
for (var i = 1; i < arguments.length; i++)
rest.push(arguments[i]);
if (action.callback)
action.callback.apply(this, rest);
this._actionQueue.shift();
this._act();
};

View File

@ -7,8 +7,11 @@ function onLoad () {
var x = node.path.join(fixtures, "x.txt");
file = new node.fs.File;
file.open(x, "r", function (status) {
assertTrue(status == 0);
file.onError = function (method, errno, msg) {
assertTrue(false);
};
file.open(x, "r", function () {
assert_count += 1;
file.close();
});

View File

@ -80,11 +80,11 @@ a:hover { text-decoration: underline; }
<ol>
<li><a href="#benchmarks">Benchmarks</a></li>
<li><a href="#download">Download</a></li>
<li><a href="#install">Build</a></li>
<li><a href="#build">Build</a></li>
<li><a href="#api">API</a>
<ol>
<li><a href="#timers">Timers</a>
<li><a href="#files">File System I/O</a>
<li><a href="#files">File I/O</a>
<li><a href="#tcp">TCP</a>
<ol>
<li><a href="#tcp_server">Server</a>
@ -166,12 +166,15 @@ always have a capital first letter.
<dl>
<dt><code class="sh_javascript">puts(string, callback)</code></dt>
<dd>Outputs the <code>string</code> and a trailing new-line to <code>stdout</code>.
<dd>
Alias for <code class="sh_javascript">stdout.puts()</code>.
Outputs the <code>string</code> and a trailing new-line to <code>stdout</code>.
<p>The <code>callback</code> argument is optional and mostly useless: it will
notify the user when the operation has completed. Everything in node is
asynchronous; <code>puts()</code> is no exception. This might seem ridiculous
but, if for example, one is piping their output into an NFS'd file,
<code>printf()</code> will block.
but, if for example, one is piping <code>stdout</code> into an NFS file,
<code>printf()</code> will block from network latency.
There is an internal queue for <code>puts()</code> output, so you can be assured that
output will be displayed in the order it was called.
</dd>
@ -204,45 +207,80 @@ always have a capital first letter.
<dd> Stops a interval from triggering. </dd>
</dl>
<h3 id="files">node.File</h3>
<h3 id="files">node.fs</h3>
<p> File system I/O has always been tricky because there are not any portable
non-blocking ways to do it. To get around this, Node uses <a
<p>Because there are not non-blocking ways to do it, asynchronous file I/O is
tricky. Node handles file I/O by employing <a
href="http://software.schmorp.de/pkg/libeio.html">an internal thread pool</a>
to execute file system calls asynchronously.
to execute file system calls.
<p>Internal request queues exist for each file object so that multiple commands
can be issued at once without worry that they will be executed out-of-order.
Thus the following is safe:
<p>All file I/O calls are rather thin wrappers around standard POSIX functions.
All calls have an optional last callback parameter
<p>Internal request queues exist for each file object so multiple commands can
be issued at once without worry that they will reach the file system out of
order. Thus the following is safe:
<pre class="sh_javascript">
var file = new node.File();
var file = new node.fs.File();
file.open("/tmp/blah", "w+");
file.write("hello ");
file.write("hello");
file.write("world");
file.close();</pre>
Additionally there is a process-wide queue for all commands which operate on
the file system directory structure (like <code>rename</code> and
<code>unlink</code>). It's important to understand that all of these request queues are
distinct. If, for example, you do
<p>
It's important to understand that the request queues are local to a single file.
If one does
<pre class="sh_javascript">fileA.write("hello");
fileB.write("world");</pre>
it could be that
first <code>fileB</code> gets written to and then <code>fileA</code> gets written to.
So if a certain operation order is needed involving multiple files, use the
it could be that <code>fileB</code> gets written to before <code>fileA</code>
is written to.
If a certain operation order is needed involving multiple files, use the
completion callbacks:
<pre class="sh_javascript">fileA.write("hello", function () {
fileB.write("world");
});</pre>
<dl>
<dt><code class="sh_javascript">node.File.rename()</code></dt>
<dt><code class="sh_javascript">new node.fs.File</code></dt>
<dd>Creates a new file object. </dd>
<dt><code class="sh_javascript">file.open(path, mode, on_completion)</code></dt>
<dd>Opens the file at <code>path</code>.
<p><code>mode</code> is a string:
<code>"r"</code> open for reading and writing.
<code>"r+"</code> open for only reading.
<code>"w"</code> create a new file for reading and writing; if it
already exists truncate it.
<code>"w+"</code> create a new file for writing only; if it already
exists truncate it.
<code>"a"</code> create a new file for writing and reading. Writes
append to the end of the file.
<code>"a+"</code>
<p>The <code>on_completion</code> is a callback that is made without
arguments when the operation completes. It is optional
If an error occurred the <code>on_completion</code> callback will not be
called, but the <code>file.onError</code> will be called.
</dd>
<dt><code class="sh_javascript">file.read(length, position, on_completion)</code></dt>
<dd>
</dd>
<dt><code class="sh_javascript">file.write(data, position, on_completion)</code></dt>
<dd>
</dd>
<dt><code class="sh_javascript">file.close(on_completion)</code></dt>
<dd>
</dd>
</dl>
<h4>File System Operations</h4>
<dl>
<dt><code class="sh_javascript">node.fs.rename(path1, path2, on_completion)</code></dt>
<dd>
</dd>
<dt><code class="sh_javascript">node.fs.stat(path1, on_completion)</code></dt>
<dd>
</dd>
</dl>
@ -250,6 +288,10 @@ completion callbacks:
<h3 id="tcp"><code>node.tcp</code></h3>
<h4 id="tcp_server"><code>node.tcp.Server</code></h4>
<h4 id="tcp_connection"><code>node.tcp.Connection</code></h4>
<h3 id="http"><code>node.http</code></h3>
<p> Node provides a web server and client interface. The interface is rather
@ -410,10 +452,11 @@ res.sendHeader(200, [ ["Content-Length", body.length]
<h4 id="http_client"><code class="sh_javascript">node.http.Client</code></h4>
<p> An HTTP client is constructed with a server address as its argument, then
the user issues one or more requests. Depending on the server connected to,
the client might pipeline the requests or reestablish the connection after each
connection. <i>Currently the client does not pipeline requests.</i>
<p> An HTTP client is constructed with a server address as its argument, the
returned handle is then used to issue one or more requests. Depending on the
server connected to, the client might pipeline the requests or reestablish the
connection after each connection.
<i>Currently the implementation does not pipeline requests.</i>
<p> Example of connecting to <code>google.com</code>
<pre class="sh_javascript">
@ -441,7 +484,9 @@ request is issued.
<dt><code class="sh_javascript">client.post(path, request_headers);</code></dt>
<dt><code class="sh_javascript">client.del(path, request_headers);</code></dt>
<dt><code class="sh_javascript">client.put(path, request_headers);</code></dt>
<dd> Issues a request.
<dd> Issues a request; if necessary establishes connection.
<p>
<code>request_headers</code> is optional.
<code>request_headers</code> should be an array of 2-element arrays.
Additional request headers might be added internally by Node.
@ -450,15 +495,18 @@ request is issued.
<p>Important: the request is not complete. This method only sends the
header of the request. One needs to call <code>req.finish()</code> to finalize
the request and retrieve the response. (This sounds convoluted but it provides
a chance for the user to stream a body to the server with
<code>req.sendBody</code>. <code>GET</code> and <code>HEAD</code> requests
normally are without bodies but HTTP does not forbid it, so neither do we.)
a chance for the user to stream a body to the server with <code
class="sh_javascript">req.sendBody()</code>.)
<p><i> <code>GET</code> and
<code>HEAD</code> requests normally are without bodies but HTTP does not forbid
it, so neither do we.</i>
</dl>
<h4 id="http_client_request"><code class="sh_javascript">node.http.ClientRequest</code></h4>
<p>This object created internally and returned from the request methods of a
<p>This object is created internally and returned from the request methods of a
<code>node.http.Client</code>. It represents an <i>in-progress</i> request
whose header has already been sent.
@ -500,7 +548,7 @@ read.
<dl>
<dt><code class="sh_javascript">res.statusCode</code></dt>
<dd>The 3-digit HTTP response status code. (E.G. <code class="sh_javascript">404</code>.)</dd>
<dd>The 3-digit HTTP response status code. E.G. <code class="sh_javascript">404</code>.</dd>
<dt><code class="sh_javascript">res.httpVersion</code></dt>
<dd>The HTTP version of the connected-to server. Probably either
@ -513,7 +561,7 @@ read.
<dt><code class="sh_javascript">res.onBody</code></dt>
<dd>Callback. Should be set by the user to be informed of when a piece
of the message body is received.
of the response body is received.
A chunk of the body is given as the single argument. The transfer-encoding
has been removed.