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>
{
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>
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);
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;
}
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();
}
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));
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;
}
{
HandleScope scope;
encoding_ = RAW;
- InitActionQueue(handle);
+ handle->Set(ACTION_QUEUE_SYMBOL, Array::New());
}
File::~File ()
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);
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) {
File *file = NODE_UNWRAP(File, args.Holder());
+ off_t pos = args[1]->IntegerValue();
+
char *buf = NULL;
size_t length = 0;
// 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();
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();
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;
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();
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();
-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);
});
}
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
});
}
- 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);
};
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.
// 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
- });
+// See File::CallTopCallback() in file.cc for the other side of the binding.
+
+
+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._act = node.fs.File.prototype._act = function () {
+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();
};
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();
});
<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>
<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>
<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.
-
-
-<p>All file I/O calls are rather thin wrappers around standard POSIX functions.
-All calls have an optional last callback parameter
+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>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>
<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
<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">
<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.
<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.
<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
<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.