Browse Source

Implement Promises for file i/o

v0.7.4-release
Ryan 16 years ago
parent
commit
65324866bc
  1. 26
      src/events.cc
  2. 14
      src/events.h
  3. 15
      src/events.js
  4. 233
      src/file.cc
  5. 151
      src/file.js
  6. 28
      src/http.js
  7. 2
      src/node.h
  8. 10
      src/node.js
  9. 16
      test/mjsunit/test-file-cat-noexist.js
  10. 8
      test/mjsunit/test-file-open.js
  11. 27
      test/mjsunit/test-http-cat.js
  12. 6
      test/mjsunit/test-http.js
  13. 33
      test/mjsunit/test-node-cat.js

26
src/events.cc

@ -66,6 +66,7 @@ Promise::Initialize (v8::Handle<v8::Object> target)
Local<FunctionTemplate> t = FunctionTemplate::New(); Local<FunctionTemplate> t = FunctionTemplate::New();
constructor_template = Persistent<FunctionTemplate>::New(t); constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->Inherit(EventEmitter::constructor_template); constructor_template->Inherit(EventEmitter::constructor_template);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
// All prototype methods are defined in events.js // All prototype methods are defined in events.js
@ -80,9 +81,34 @@ Promise::Create (void)
Local<Object> handle = Local<Object> handle =
Promise::constructor_template->GetFunction()->NewInstance(); Promise::constructor_template->GetFunction()->NewInstance();
Promise *promise = new Promise(handle); Promise *promise = new Promise(handle);
ObjectWrap::InformV8ofAllocation(promise); ObjectWrap::InformV8ofAllocation(promise);
promise->Attach();
ev_unref(EV_DEFAULT_UC);
return promise; return promise;
} }
bool
Promise::EmitSuccess (int argc, v8::Handle<v8::Value> argv[])
{
bool r = Emit("Success", argc, argv);
Detach();
ev_ref(EV_DEFAULT_UC);
return r;
}
bool
Promise::EmitError (int argc, v8::Handle<v8::Value> argv[])
{
bool r = Emit("Error", argc, argv);
Detach();
ev_ref(EV_DEFAULT_UC);
return r;
}

14
src/events.h

@ -14,6 +14,7 @@ class EventEmitter : public ObjectWrap {
bool Emit (const char *type, int argc, v8::Handle<v8::Value> argv[]); bool Emit (const char *type, int argc, v8::Handle<v8::Value> argv[]);
protected:
EventEmitter (v8::Handle<v8::Object> handle) EventEmitter (v8::Handle<v8::Object> handle)
: ObjectWrap(handle) { } : ObjectWrap(handle) { }
}; };
@ -24,18 +25,13 @@ class Promise : public EventEmitter {
static v8::Persistent<v8::FunctionTemplate> constructor_template; static v8::Persistent<v8::FunctionTemplate> constructor_template;
virtual size_t size (void) { return sizeof(Promise); }; virtual size_t size (void) { return sizeof(Promise); };
bool EmitSuccess (int argc, v8::Handle<v8::Value> argv[]) bool EmitSuccess (int argc, v8::Handle<v8::Value> argv[]);
{ bool EmitError (int argc, v8::Handle<v8::Value> argv[]);
return Emit("Success", argc, argv);
}
bool EmitError (int argc, v8::Handle<v8::Value> argv[])
{
return Emit("Error", argc, argv);
}
static Promise* Create (void); static Promise* Create (void);
v8::Handle<v8::Object> Handle(void) { return handle_; }
protected: protected:
Promise (v8::Handle<v8::Object> handle) : EventEmitter(handle) { } Promise (v8::Handle<v8::Object> handle) : EventEmitter(handle) { }

15
src/events.js

@ -25,8 +25,13 @@ emitter.emit = function (type, args) {
if (!this._events.hasOwnProperty(type)) return; if (!this._events.hasOwnProperty(type)) return;
var listeners = this._events[type]; var listeners = this._events[type];
var length = listeners.length; var length = listeners.length;
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
if (args) {
listeners[i].apply(this, args); listeners[i].apply(this, args);
} else {
listeners[i].call(this);
}
} }
}; };
@ -35,10 +40,20 @@ var promise = node.Promise.prototype;
promise.addCallback = function (listener) { promise.addCallback = function (listener) {
this.addListener("Success", listener); this.addListener("Success", listener);
return this;
}; };
promise.addErrback = function (listener) { promise.addErrback = function (listener) {
this.addListener("Error", listener); this.addListener("Error", listener);
return this;
};
promise.emitSuccess = function (args) {
this.emit("Success", args);
};
promise.emitError = function (args) {
this.emit("Error", args);
}; };
})(); // end anonymous namespace })(); // end anonymous namespace

233
src/file.cc

@ -1,5 +1,6 @@
#include "node.h" #include "node.h"
#include "file.h" #include "file.h"
#include "events.h"
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
@ -27,52 +28,44 @@ using namespace node;
#define CTIME_SYMBOL String::NewSymbol("ctime") #define CTIME_SYMBOL String::NewSymbol("ctime")
#define BAD_ARGUMENTS String::New("Bad argument") #define BAD_ARGUMENTS String::New("Bad argument")
#define MAKE_CALLBACK_PTR \ static int
Persistent<Function> *callback = NULL; \ AfterClose (eio_req *req)
Local<Value> last_arg = args[args.Length()-1]; \ {
if (last_arg->IsFunction()) { \ Promise *promise = reinterpret_cast<Promise*>(req->data);
Local<Function> l = Local<Function>::Cast(last_arg); \ if (req->result == 0) {
callback = new Persistent<Function>(); \ promise->EmitSuccess(0, NULL);
*callback = Persistent<Function>::New(l); \ } else {
} \ promise->EmitError(0, NULL);
ev_ref(EV_DEFAULT_UC); }
return 0;
#define CALL_CALLBACK_PTR(req, argc, argv) \ }
do { \
if (req->data) { \ static Handle<Value>
Persistent<Function> *callback = \ Close (const Arguments& args)
reinterpret_cast<Persistent<Function>*>(req->data); \
TryCatch try_catch; \
(*callback)->Call(Context::GetCurrent()->Global(), argc, argv); \
if(try_catch.HasCaught()) \
node::FatalException(try_catch); \
delete callback; \
} \
ev_unref(EV_DEFAULT_UC); \
} while(0)
#define DEFINE_SIMPLE_CB(name) \
static int After##name (eio_req *req) \
{ \
HandleScope scope; \
Local<Value> argv[] = { Integer::New(req->errorno) }; \
CALL_CALLBACK_PTR(req, 1, argv); \
return 0; \
} \
DEFINE_SIMPLE_CB(Close)
static Handle<Value> Close (const Arguments& args)
{ {
if (args.Length() < 1 || !args[0]->IsInt32()) if (args.Length() < 1 || !args[0]->IsInt32())
return ThrowException(BAD_ARGUMENTS); return ThrowException(BAD_ARGUMENTS);
HandleScope scope; HandleScope scope;
int fd = args[0]->Int32Value(); int fd = args[0]->Int32Value();
MAKE_CALLBACK_PTR
eio_close(fd, EIO_PRI_DEFAULT, AfterClose, callback); Promise *promise = Promise::Create();
return Undefined();
eio_close(fd, EIO_PRI_DEFAULT, AfterClose, promise);
return scope.Close(promise->Handle());
}
static int
AfterRename (eio_req *req)
{
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result == 0) {
promise->EmitSuccess(0, NULL);
} else {
promise->EmitError(0, NULL);
}
return 0;
} }
DEFINE_SIMPLE_CB(Rename)
static Handle<Value> Rename (const Arguments& args) static Handle<Value> Rename (const Arguments& args)
{ {
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString())
@ -80,44 +73,72 @@ static Handle<Value> Rename (const Arguments& args)
HandleScope scope; HandleScope scope;
String::Utf8Value path(args[0]->ToString()); String::Utf8Value path(args[0]->ToString());
String::Utf8Value new_path(args[1]->ToString()); String::Utf8Value new_path(args[1]->ToString());
MAKE_CALLBACK_PTR
eio_rename(*path, *new_path, EIO_PRI_DEFAULT, AfterRename, callback); Promise *promise = Promise::Create();
return Undefined();
eio_rename(*path, *new_path, EIO_PRI_DEFAULT, AfterRename, promise);
return scope.Close(promise->Handle());
}
static int
AfterUnlink (eio_req *req)
{
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result == 0) {
promise->EmitSuccess(0, NULL);
} else {
promise->EmitError(0, NULL);
}
return 0;
} }
DEFINE_SIMPLE_CB(Unlink)
static Handle<Value> Unlink (const Arguments& args) static Handle<Value> Unlink (const Arguments& args)
{ {
if (args.Length() < 1 || !args[0]->IsString()) if (args.Length() < 1 || !args[0]->IsString())
return ThrowException(BAD_ARGUMENTS); return ThrowException(BAD_ARGUMENTS);
HandleScope scope; HandleScope scope;
String::Utf8Value path(args[0]->ToString()); String::Utf8Value path(args[0]->ToString());
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
eio_unlink(*path, EIO_PRI_DEFAULT, AfterUnlink, callback); eio_unlink(*path, EIO_PRI_DEFAULT, AfterUnlink, promise);
return Undefined(); return scope.Close(promise->Handle());
}
static int
AfterRMDir (eio_req *req)
{
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result == 0) {
promise->EmitSuccess(0, NULL);
} else {
promise->EmitError(0, NULL);
}
return 0;
} }
DEFINE_SIMPLE_CB(RMDir)
static Handle<Value> RMDir (const Arguments& args) static Handle<Value> RMDir (const Arguments& args)
{ {
if (args.Length() < 1 || !args[0]->IsString()) if (args.Length() < 1 || !args[0]->IsString())
return ThrowException(BAD_ARGUMENTS); return ThrowException(BAD_ARGUMENTS);
HandleScope scope; HandleScope scope;
String::Utf8Value path(args[0]->ToString()); String::Utf8Value path(args[0]->ToString());
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
eio_rmdir(*path, EIO_PRI_DEFAULT, AfterRMDir, callback); eio_rmdir(*path, EIO_PRI_DEFAULT, AfterRMDir, promise);
return Undefined(); return scope.Close(promise->Handle());
} }
static int static int
AfterOpen (eio_req *req) AfterOpen (eio_req *req)
{ {
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result < 0) {
promise->EmitError(0, NULL);
return 0;
}
HandleScope scope; HandleScope scope;
const int argc = 2; Local<Value> argv[1] = { Integer::New(req->result) };
Local<Value> argv[argc]; promise->EmitSuccess(2, argv);
argv[0] = Integer::New(req->errorno);
argv[1] = Integer::New(req->result);
CALL_CALLBACK_PTR(req, argc, argv);
return 0; return 0;
} }
@ -135,27 +156,31 @@ Open (const Arguments& args)
int flags = args[1]->Int32Value(); int flags = args[1]->Int32Value();
mode_t mode = static_cast<mode_t>(args[2]->Int32Value()); mode_t mode = static_cast<mode_t>(args[2]->Int32Value());
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
eio_open(*path, flags, mode, EIO_PRI_DEFAULT, AfterOpen, callback); eio_open(*path, flags, mode, EIO_PRI_DEFAULT, AfterOpen, promise);
return Undefined(); return scope.Close(promise->Handle());
} }
static int static int
AfterWrite (eio_req *req) AfterWrite (eio_req *req)
{ {
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result < 0) {
promise->EmitError(0, NULL);
return 0;
}
HandleScope scope; HandleScope scope;
free(req->ptr2); free(req->ptr2);
ssize_t written = req->result; ssize_t written = req->result;
Local<Value> argv[1];
argv[0] = written >= 0 ? Integer::New(written) : Integer::New(0);
const int argc = 2; promise->EmitSuccess(1, argv);
Local<Value> argv[argc];
argv[0] = Integer::New(req->errorno);
argv[1] = written >= 0 ? Integer::New(written) : Integer::New(0);
CALL_CALLBACK_PTR(req, argc, argv);
return 0; return 0;
} }
@ -206,57 +231,63 @@ Write (const Arguments& args)
return ThrowException(BAD_ARGUMENTS); return ThrowException(BAD_ARGUMENTS);
} }
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
eio_write(fd, buf, len, pos, EIO_PRI_DEFAULT, AfterWrite, callback); eio_write(fd, buf, len, pos, EIO_PRI_DEFAULT, AfterWrite, promise);
return Undefined(); return scope.Close(promise->Handle());
} }
static int static int
AfterUtf8Read (eio_req *req) AfterUtf8Read (eio_req *req)
{ {
Promise *promise = reinterpret_cast<Promise*>(req->data);
if (req->result < 0) {
promise->EmitError(0, NULL);
return 0;
}
HandleScope scope; HandleScope scope;
const int argc = 2; Local<Value> argv[1];
Local<Value> argv[argc];
argv[0] = Integer::New(req->errorno);
char *buf = reinterpret_cast<char*>(req->ptr2);
if (req->result == 0) { if (req->result == 0) {
// eof // eof
argv[1] = Local<Value>::New(Null()); argv[0] = Local<Value>::New(Null());
} else { } else {
argv[1] = String::New(buf, req->result); char *buf = reinterpret_cast<char*>(req->ptr2);
argv[0] = String::New(buf, req->result);
} }
CALL_CALLBACK_PTR(req, argc, argv); promise->EmitSuccess(1, argv);
return 0; return 0;
} }
static int static int
AfterRawRead(eio_req *req) AfterRawRead(eio_req *req)
{ {
HandleScope scope; Promise *promise = reinterpret_cast<Promise*>(req->data);
const int argc = 2; if (req->result < 0) {
Local<Value> argv[argc]; promise->EmitError(0, NULL);
argv[0] = Integer::New(req->errorno); return 0;
}
char *buf = reinterpret_cast<char*>(req->ptr2); HandleScope scope;
Local<Value> argv[1];
if (req->result == 0) { if (req->result == 0) {
// eof argv[0] = Local<Value>::New(Null());
argv[1] = Local<Value>::New(Null());
} else { } else {
// raw encoding char *buf = reinterpret_cast<char*>(req->ptr2);
size_t len = req->result; size_t len = req->result;
Local<Array> array = Array::New(len); Local<Array> array = Array::New(len);
for (unsigned int i = 0; i < len; i++) { for (unsigned int i = 0; i < len; i++) {
array->Set(Integer::New(i), Integer::New(buf[i])); array->Set(Integer::New(i), Integer::New(buf[i]));
} }
argv[1] = array; argv[0] = array;
} }
CALL_CALLBACK_PTR(req, argc, argv); promise->EmitSuccess(1, argv);
return 0; return 0;
} }
@ -274,7 +305,7 @@ AfterRawRead(eio_req *req)
static Handle<Value> static Handle<Value>
Read (const Arguments& args) Read (const Arguments& args)
{ {
if ( args.Length() < 3 if ( args.Length() < 2
|| !args[0]->IsInt32() // fd || !args[0]->IsInt32() // fd
|| !args[1]->IsNumber() // len || !args[1]->IsNumber() // len
) return ThrowException(BAD_ARGUMENTS); ) return ThrowException(BAD_ARGUMENTS);
@ -290,26 +321,31 @@ Read (const Arguments& args)
encoding = static_cast<enum encoding>(args[3]->Int32Value()); encoding = static_cast<enum encoding>(args[3]->Int32Value());
} }
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
// NOTE: 2nd param: NULL pointer tells eio to allocate it itself // NOTE: 2nd param: NULL pointer tells eio to allocate it itself
eio_read(fd, NULL, len, pos, EIO_PRI_DEFAULT, eio_read(fd, NULL, len, pos, EIO_PRI_DEFAULT,
encoding == UTF8 ? AfterUtf8Read : AfterRawRead, callback); encoding == UTF8 ? AfterUtf8Read : AfterRawRead, promise);
return Undefined();
return scope.Close(promise->Handle());
} }
static int static int
AfterStat (eio_req *req) AfterStat (eio_req *req)
{ {
HandleScope scope; Promise *promise = reinterpret_cast<Promise*>(req->data);
const int argc = 2; if (req->result < 0) {
Local<Value> argv[argc]; promise->EmitError(0, NULL);
argv[0] = Integer::New(req->errorno); return 0;
}
HandleScope scope;
Local<Value> argv[1];
Local<Object> stats = Object::New(); Local<Object> stats = Object::New();
argv[1] = stats; argv[1] = stats;
if (req->result == 0) {
struct stat *s = reinterpret_cast<struct stat*>(req->ptr2); struct stat *s = reinterpret_cast<struct stat*>(req->ptr2);
/* ID of device containing file */ /* ID of device containing file */
@ -338,8 +374,9 @@ AfterStat (eio_req *req)
stats->Set(MTIME_SYMBOL, Date::New(1000*static_cast<double>(s->st_mtime))); stats->Set(MTIME_SYMBOL, Date::New(1000*static_cast<double>(s->st_mtime)));
/* time of last status change */ /* time of last status change */
stats->Set(CTIME_SYMBOL, Date::New(1000*static_cast<double>(s->st_ctime))); stats->Set(CTIME_SYMBOL, Date::New(1000*static_cast<double>(s->st_ctime)));
}
CALL_CALLBACK_PTR(req, argc, argv); \ promise->EmitSuccess(1, argv);
return 0; return 0;
} }
@ -353,11 +390,11 @@ Stat (const Arguments& args)
String::Utf8Value path(args[0]->ToString()); String::Utf8Value path(args[0]->ToString());
MAKE_CALLBACK_PTR Promise *promise = Promise::Create();
eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, callback); eio_stat(*path, EIO_PRI_DEFAULT, AfterStat, promise);
return Undefined(); return scope.Close(promise->Handle());
} }
static Handle<Value> static Handle<Value>

151
src/file.js

@ -1,23 +1,26 @@
node.fs.exists = function (path, callback) { node.fs.exists = function (path, callback) {
node.fs.stat(path, function (status) { var p = node.fs.stat(path);
callback(status == 0); p.addCallback(function () { callback(true); });
}); p.addErrback(function () { callback(false); });
} }
node.fs.cat = function (path, encoding, callback) { node.fs.cat = function (path, encoding, callback) {
var file = new node.fs.File({encoding: encoding}); var open_promise = node.fs.open(path, node.O_RDONLY, 0666);
var cat_promise = new node.Promise();
file.onError = function (method, errno, msg) { encoding = (encoding === "raw" ? node.RAW : node.UTF8);
//node.debug("cat error");
callback(-1);
};
open_promise.addErrback(function () { cat_promise.emitError(); });
open_promise.addCallback(function (fd) {
var content = (encoding == node.UTF8 ? "" : []); var content = (encoding == node.UTF8 ? "" : []);
var pos = 0; var pos = 0;
var chunkSize = 16*1024;
function readChunk () { function readChunk () {
file.read(chunkSize, pos, function (chunk) { var read_promise = node.fs.read(fd, 16*1024, pos, encoding);
read_promise.addErrback(function () { cat_promise.emitError(); });
read_promise.addCallback(function (chunk) {
if (chunk) { if (chunk) {
if (chunk.constructor == String) if (chunk.constructor == String)
content += chunk; content += chunk;
@ -27,17 +30,20 @@ node.fs.cat = function (path, encoding, callback) {
pos += chunk.length; pos += chunk.length;
readChunk(); readChunk();
} else { } else {
callback(0, content); cat_promise.emitSuccess([content]);
file.close(); node.fs.close(fd);
} }
}); });
} }
readChunk();
file.open(path, "r", function () { readChunk(); }); });
return cat_promise;
}; };
node.fs.File = function (options) { node.fs.File = function (options) {
var self = this; var self = this;
self.__proto__ = new node.EventEmitter();
options = options || {}; options = options || {};
if (options.encoding === "utf8") { if (options.encoding === "utf8") {
@ -45,58 +51,65 @@ node.fs.File = function (options) {
} else { } else {
self.encoding = node.RAW; self.encoding = node.RAW;
} }
//node.debug("encoding: opts=" + options.encoding + " self=" + self.encoding); //node.debug("encoding: opts=" + options.encoding + " self=" + self.encoding);
self.fd = options.fd || null; self.fd = options.fd || null;
var actionQueue = []; var actionQueue = [];
// Adds a method to the queue. // Adds a method to the queue.
function addAction (method, args, callback) { function createAction (method, args) {
var action = { method: method var promise = new node.Promise();
, callback: callback
, args: args promise.method = method;
}; promise.args = args;
//node.debug("add action: " + JSON.stringify(action)); //node.debug("add action: " + JSON.stringify(action));
actionQueue.push(action); actionQueue.push(promise);
// If the queue was empty, immediately call the method. // If the queue was empty, immediately call the method.
if (actionQueue.length == 1) act(); if (actionQueue.length == 1) act();
return promise;
} }
// called after each action finishes (when it returns from the thread pool) function act () {
function poll () { var promise = actionQueue[0]; // peek at the head of the queue
var action = actionQueue[0]; if (promise) {
node.debug("internal apply " + JSON.stringify(promise.args));
internal_methods[promise.method].apply(self, promise.args);
}
}
var errno = arguments[0]; // called after each action finishes (when it returns from the thread pool)
function success () {
var promise = actionQueue[0];
//node.debug("poll errno: " + JSON.stringify(errno)); if (!promise) throw "actionQueue empty when it shouldn't be.";
//node.debug("poll action: " + JSON.stringify(action));
//node.debug("poll rest: " + JSON.stringify(rest));
if (errno !== 0) { var args = [];
if (self.onError) for (var i = 0; i < arguments.length; i++) {
self.onError(action.method, errno, node.fs.strerror(errno)); node.debug(JSON.stringify(arguments[i]));
actionQueue = []; // empty the queue. args.push(arguments[i]);
return;
} }
var rest = []; promise.emitSuccess(args);
for (var i = 1; i < arguments.length; i++)
rest.push(arguments[i]);
if (action.callback)
action.callback.apply(this, rest);
actionQueue.shift(); actionQueue.shift();
act(); act();
} }
function act () { function error () {
var action = actionQueue[0]; // peek at the head of the queue var promise = actionQueue[0];
if (action) {
internal_methods[action.method].apply(this, action.args); if (!promise) throw "actionQueue empty when it shouldn't be.";
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
} }
promise.emitError(args);
self.emitError(args);
} }
var internal_methods = { var internal_methods = {
@ -125,51 +138,63 @@ node.fs.File = function (options) {
throw "Unknown mode"; throw "Unknown mode";
} }
// fix the mode here // fix the mode here
node.fs.open(path, flags, 0666, function (status, fd) { var promise = node.fs.open(path, flags, 0666);
promise.addCallback(function (fd) {
self.fd = fd; self.fd = fd;
poll(status, fd); success(fd);
}); });
promise.addErrback(error);
}, },
close: function ( ) { close: function ( ) {
node.fs.close(self.fd, function (status) { var promise = node.fs.close(self.fd);
promise.addCallback(function () {
self.fd = null; self.fd = null;
poll(status); success();
}); });
promise.addErrback(error);
}, },
read: function (length, position) { read: function (length, position) {
//node.debug("encoding: " + self.encoding); //node.debug("encoding: " + self.encoding);
node.fs.read(self.fd, length, position, self.encoding, poll); var promise = node.fs.read(self.fd, length, position, self.encoding);
promise.addCallback(success);
promise.addErrback(error);
}, },
write: function (data, position) { write: function (data, position) {
node.fs.write(self.fd, data, position, poll); var promise = node.fs.write(self.fd, data, position);
promise.addCallback(success);
promise.addErrback(error);
} }
}; };
self.open = function (path, mode, callback) { self.open = function (path, mode) {
addAction("open", [path, mode], callback); return createAction("open", [path, mode]);
}; };
self.close = function (callback) { self.close = function () {
addAction("close", [], callback); return createAction("close", []);
}; };
self.read = function (length, pos, callback) { self.read = function (length, pos) {
addAction("read", [length, pos], callback); return createAction("read", [length, pos]);
}; };
self.write = function (buf, pos, callback) { self.write = function (buf, pos) {
addAction("write", [buf, pos], callback); return createAction("write", [buf, pos]);
}; };
self.print = function (data, callback) { self.print = function (data) {
return self.write(data, null, callback); return self.write(data, null);
}; };
self.puts = function (data, callback) { self.puts = function (data) {
return self.write(data + "\n", null, callback); return self.write(data + "\n", null);
}; };
}; };
@ -179,6 +204,4 @@ stdin = new node.fs.File({ fd: node.STDIN_FILENO });
puts = stdout.puts; puts = stdout.puts;
print = stdout.print; print = stdout.print;
p = function (data, callback) { p = function (data) { return puts(JSON.stringify(data)); }
puts(JSON.stringify(data), callback);
}

28
src/http.js

@ -537,20 +537,32 @@ function createClientRequest (connection, method, uri, header_lines) {
return req; return req;
} }
node.http.cat = function (url, encoding, callback) { node.http.cat = function (url, encoding) {
var promise = new node.Promise();
var uri = node.http.parseUri(url); var uri = node.http.parseUri(url);
var req = node.http.createClient(uri.port || 80, uri.host).get(uri.path || "/"); var client = node.http.createClient(uri.port || 80, uri.host);
var req = client.get(uri.path || "/");
client.addListener("Error", function () {
promise.emitError();
});
var content = "";
req.finish(function (res) { req.finish(function (res) {
var status = res.statusCode == 200 ? 0 : -1; if (res.statusCode < 200 || res.statusCode >= 300) {
promise.emitError([res.statusCode]);
return;
}
res.setBodyEncoding(encoding); res.setBodyEncoding(encoding);
var content = ""; res.addListener("Body", function (chunk) { content += chunk; });
res.addListener("Body", function (chunk) {
content += chunk;
});
res.addListener("BodyComplete", function () { res.addListener("BodyComplete", function () {
callback(status, content); promise.emitSuccess([content]);
}); });
}); });
return promise;
}; };
})(); // anonymous namespace })(); // anonymous namespace

2
src/node.h

@ -44,7 +44,6 @@ public:
protected: protected:
static void* Unwrap (v8::Handle<v8::Object> handle); static void* Unwrap (v8::Handle<v8::Object> handle);
v8::Persistent<v8::Object> handle_;
/* Attach() marks the object as being attached to an event loop. /* Attach() marks the object as being attached to an event loop.
* Attached objects will not be garbage collected, even if * Attached objects will not be garbage collected, even if
@ -60,6 +59,7 @@ protected:
* persistant handle.) * persistant handle.)
*/ */
void Detach(); void Detach();
v8::Persistent<v8::Object> handle_;
private: private:
static void MakeWeak (v8::Persistent<v8::Value> _, void *data); static void MakeWeak (v8::Persistent<v8::Value> _, void *data);

10
src/node.js

@ -72,7 +72,7 @@ node.path = new function () {
node.cat = function(location, encoding, callback) { node.cat = function(location, encoding, callback) {
var url_re = new RegExp("^http:\/\/"); var url_re = new RegExp("^http:\/\/");
var f = url_re.exec(location) ? node.http.cat : node.fs.cat; var f = url_re.exec(location) ? node.http.cat : node.fs.cat;
f(location, encoding, callback); return f(location, encoding, callback);
}; };
// Module // Module
@ -105,12 +105,14 @@ node.Module.prototype.load = function (callback) {
throw "Module '" + self.filename + "' is already loaded."; throw "Module '" + self.filename + "' is already loaded.";
} }
node.cat(self.filename, "utf8", function (status, content) { var promise = node.cat(self.filename, "utf8");
if (status != 0) {
promise.addErrback(function () {
stderr.puts("Error reading " + self.filename); stderr.puts("Error reading " + self.filename);
node.exit(1); node.exit(1);
} });
promise.addCallback(function (content) {
self.target.__require = function (path) { return self.newChild(path, {}); }; self.target.__require = function (path) { return self.newChild(path, {}); };
self.target.__include = function (path) { self.newChild(path, self.target); }; self.target.__include = function (path) { self.newChild(path, self.target); };

16
test/mjsunit/test-file-cat-noexist.js

@ -1,13 +1,23 @@
include("mjsunit.js"); include("mjsunit.js");
var status = null; var got_error = false;
function onLoad () { function onLoad () {
var dirname = node.path.dirname(__filename); var dirname = node.path.dirname(__filename);
var fixtures = node.path.join(dirname, "fixtures"); var fixtures = node.path.join(dirname, "fixtures");
var filename = node.path.join(fixtures, "does_not_exist.txt"); var filename = node.path.join(fixtures, "does_not_exist.txt");
node.fs.cat(filename, "raw", function (s) { status = s }); var promise = node.fs.cat(filename, "raw");
promise.addCallback(function (content) {
node.debug("cat returned some content: " + content);
node.debug("this shouldn't happen as the file doesn't exist...");
assertTrue(false);
});
promise.addErrback(function () {
got_error = true;
});
} }
function onExit () { function onExit () {
assertTrue(status != 0); assertTrue(got_error);
} }

8
test/mjsunit/test-file-open.js

@ -10,14 +10,16 @@ function onLoad () {
var x = node.path.join(fixtures, "x.txt"); var x = node.path.join(fixtures, "x.txt");
file = new node.fs.File; file = new node.fs.File;
file.onError = function () { got_error = true }; file.addListener("Error", function () { got_error = true });
file.open(x, "r", function () { file.open(x, "r").addCallback(function () {
opened = true opened = true
file.close(function () { file.close().addCallback(function () {
closed = true; closed = true;
}); });
}); });
puts("hey!");
} }
function onExit () { function onExit () {

27
test/mjsunit/test-http-cat.js

@ -12,15 +12,26 @@ var server = node.http.createServer(function (req, res) {
}); });
server.listen(PORT); server.listen(PORT);
var got_good_server_content = false;
var bad_server_got_error = false;
function onLoad() { function onLoad() {
node.http.cat("http://localhost:"+PORT, "utf8", function(status, content) { node.http.cat("http://localhost:"+PORT+"/", "utf8")
.addCallback(function (content) {
node.debug("got response");
got_good_server_content = true;
assertEquals(body, content); assertEquals(body, content);
assertEquals(0, status) server.close();
server.close() });
})
node.http.cat("http://localhost:12312/", "utf8")
.addErrback(function () {
node.debug("got error (this should happen)");
bad_server_got_error = true;
});
}
node.http.cat("http://localhost:"+PORT+1, "utf8", function(status, content) { function onExit () {
assertEquals(-1, status) assertTrue(got_good_server_content);
assertEquals(nil, content) assertTrue(bad_server_got_error);
})
} }

6
test/mjsunit/test-http.js

@ -36,6 +36,7 @@ function onLoad () {
responses_recvd += 1; responses_recvd += 1;
res.setBodyEncoding("utf8"); res.setBodyEncoding("utf8");
res.addListener("Body", function (chunk) { body0 += chunk; }); res.addListener("Body", function (chunk) { body0 += chunk; });
node.debug("Got /hello response");
}); });
setTimeout(function () { setTimeout(function () {
@ -45,13 +46,18 @@ function onLoad () {
responses_recvd += 1; responses_recvd += 1;
res.setBodyEncoding("utf8"); res.setBodyEncoding("utf8");
res.addListener("Body", function (chunk) { body1 += chunk; }); res.addListener("Body", function (chunk) { body1 += chunk; });
node.debug("Got /world response");
}); });
}, 1); }, 1);
} }
function onExit () { function onExit () {
node.debug("responses_recvd: " + responses_recvd);
assertEquals(2, responses_recvd); assertEquals(2, responses_recvd);
node.debug("responses_sent: " + responses_sent);
assertEquals(2, responses_sent); assertEquals(2, responses_sent);
assertEquals("The path was /hello", body0); assertEquals("The path was /hello", body0);
assertEquals("The path was /world", body1); assertEquals("The path was /world", body1);
} }

33
test/mjsunit/test-node-cat.js

@ -12,18 +12,39 @@ var server = node.http.createServer(function (req, res) {
}); });
server.listen(PORT); server.listen(PORT);
var errors = 0;
var successes = 0;
function onLoad() { function onLoad() {
node.cat("http://localhost:"+PORT, "utf8", function(status, content) { var promise = node.cat("http://localhost:"+PORT, "utf8");
promise.addCallback(function (content) {
assertEquals(body, content); assertEquals(body, content);
assertEquals(0, status)
server.close() server.close()
}) successes += 1;
});
promise.addErrback(function () {
errors += 1;
});
var dirname = node.path.dirname(__filename); var dirname = node.path.dirname(__filename);
var fixtures = node.path.join(dirname, "fixtures"); var fixtures = node.path.join(dirname, "fixtures");
var x = node.path.join(fixtures, "x.txt"); var x = node.path.join(fixtures, "x.txt");
node.cat(x, "utf8", function(status, content) {
assertEquals(0, status) promise = node.cat(x, "utf8");
promise.addCallback(function (content) {
assertEquals("xyz", content.replace(/[\r\n]/, '')) assertEquals("xyz", content.replace(/[\r\n]/, ''))
}) successes += 1;
});
promise.addErrback(function () {
errors += 1;
});
}
function onExit () {
assertEquals(2, successes);
assertEquals(0, errors);
} }

Loading…
Cancel
Save