Merge branch 'net2'
Expect instability on the master branch for a while. Problems: - Documentation is not yet updated - SSL support is gone! It needs to be redone for net.js. Use 'tcp_old' and 'http_old' if you need it. I want to use OpenSSL now, talk to me if you'd like to work on it. - fs.write() supports Buffers a little. See src/node_file.cc for details fs.read() not yet. The file streams need to be updated to handle Buffer. - The Buffer API will probably change.
This commit is contained in:
commit
cc053e7df7
@ -1,8 +1,5 @@
|
||||
path = require("path");
|
||||
|
||||
libDir = path.join(path.dirname(__filename), "../lib");
|
||||
require.paths.unshift(libDir);
|
||||
|
||||
var puts = require("sys").puts;
|
||||
http = require("http");
|
||||
|
||||
|
68
doc/api.txt
68
doc/api.txt
@ -192,23 +192,6 @@ The default is to only recurse twice. To make it recurse indefinitely, pass
|
||||
in +null+ for +depth+.
|
||||
|
||||
|
||||
+exec(command, callback)+::
|
||||
Executes the command as a child process, buffers the output and returns it
|
||||
in a callback.
|
||||
+
|
||||
----------------------------------------
|
||||
var sys = require("sys");
|
||||
sys.exec("ls /", function (err, stdout, stderr) {
|
||||
if (err) throw err;
|
||||
sys.puts(stdout);
|
||||
});
|
||||
----------------------------------------
|
||||
+
|
||||
The callback gets the arguments +(err, stdout, stderr)+. On success +err+
|
||||
will be +null+. On error +err+ will be an instance of +Error+ and +err.code+
|
||||
will be the exit code of the child process.
|
||||
|
||||
|
||||
== Events
|
||||
|
||||
Many objects in Node emit events: a TCP server emits an event each time
|
||||
@ -399,25 +382,18 @@ Stops a interval from triggering.
|
||||
== Child Processes
|
||||
|
||||
Node provides a tridirectional +popen(3)+ facility through the class
|
||||
+process.ChildProcess+. It is possible to stream data through the child's +stdin+,
|
||||
+stdout+, and +stderr+ in a fully non-blocking way.
|
||||
+ChildProcess+ class. It is possible to stream data through the child's
|
||||
+stdin+, +stdout+, and +stderr+ in a fully non-blocking way.
|
||||
|
||||
To create a child process use +require("child_process").spawn()+.
|
||||
|
||||
Child processes always have three streams associated with them.
|
||||
+child.stdin+, +child.stdout+, and +child.stderr+.
|
||||
|
||||
=== +process.ChildProcess+
|
||||
|
||||
[cols="1,2,10",options="header"]
|
||||
|=========================================================
|
||||
| Event | Parameters |Notes
|
||||
|
||||
| +"output"+ | +data+ | Each time the child process
|
||||
sends data to its +stdout+, this event is
|
||||
emitted. +data+ is a string. If the child
|
||||
process closes its +stdout+ stream (a common
|
||||
thing to do on exit), this event will be emitted
|
||||
with +data === null+.
|
||||
|
||||
| +"error"+ | +data+ | Identical to the +"output"+ event except for
|
||||
+stderr+ instead of +stdout+.
|
||||
|
||||
| +"exit"+ | +code+ | This event is emitted after the child process
|
||||
ends. +code+ is the final exit code of the
|
||||
process. One can be assured that after this
|
||||
@ -425,19 +401,18 @@ Node provides a tridirectional +popen(3)+ facility through the class
|
||||
+"error"+ callbacks will no longer be made.
|
||||
|=========================================================
|
||||
|
||||
+process.createChildProcess(command, args=[], env=process.env)+::
|
||||
+require("child_process").spawn(command, args=[], env=process.env)+::
|
||||
Launches a new process with the given +command+, command line arguments, and
|
||||
environmental variables. For example:
|
||||
+
|
||||
----------------------------------------
|
||||
var ls = process.createChildProcess("ls", ["-lh", "/usr"]);
|
||||
ls.addListener("output", function (data) {
|
||||
sys.puts(data);
|
||||
// Pipe a child process output to
|
||||
// parent process output
|
||||
var ls = spawn("ls", ["-lh", "/usr"]);
|
||||
ls.stdout.addListener("data", function (data) {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
----------------------------------------
|
||||
+
|
||||
Note, if you just want to buffer the output of a command and return it, then
|
||||
+exec()+ in +/sys.js+ might be better.
|
||||
|
||||
|
||||
+child.pid+ ::
|
||||
@ -459,6 +434,23 @@ Send a signal to the child process. If no argument is given, the process
|
||||
will be sent +"SIGTERM"+. See signal(7) for a list of available signals.
|
||||
|
||||
|
||||
+require("child_process").exec(command, callback)+::
|
||||
High-level way to executes a command as a child process and buffer the
|
||||
output and return it in a callback.
|
||||
+
|
||||
----------------------------------------
|
||||
var exec = require("child_process").exec;
|
||||
exec("ls /", function (err, stdout, stderr) {
|
||||
if (err) throw err;
|
||||
sys.puts(stdout);
|
||||
});
|
||||
----------------------------------------
|
||||
+
|
||||
The callback gets the arguments +(err, stdout, stderr)+. On success +err+
|
||||
will be +null+. On error +err+ will be an instance of +Error+ and +err.code+
|
||||
will be the exit code of the child process.
|
||||
|
||||
|
||||
|
||||
== File System
|
||||
|
||||
|
20
lib/buffer.js
Normal file
20
lib/buffer.js
Normal file
@ -0,0 +1,20 @@
|
||||
var Buffer = process.binding('buffer').Buffer;
|
||||
|
||||
exports.Buffer = Buffer;
|
||||
|
||||
Buffer.prototype.toString = function () {
|
||||
return this.utf8Slice(0, this.length);
|
||||
};
|
||||
|
||||
Buffer.prototype.toJSON = function () {
|
||||
return this.utf8Slice(0, this.length);
|
||||
/*
|
||||
var s = "";
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
s += this[i].toString(16) + " ";
|
||||
}
|
||||
return s;
|
||||
*/
|
||||
};
|
||||
|
||||
|
100
lib/child_process.js
Normal file
100
lib/child_process.js
Normal file
@ -0,0 +1,100 @@
|
||||
var inherits = require('sys').inherits;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Stream = require('net').Stream;
|
||||
var InternalChildProcess = process.binding('child_process').ChildProcess;
|
||||
|
||||
|
||||
var spawn = exports.spawn = function (path, args, env) {
|
||||
var child = new ChildProcess();
|
||||
child.spawn(path, args, env);
|
||||
return child;
|
||||
};
|
||||
|
||||
|
||||
exports.exec = function (command, callback) {
|
||||
var child = spawn("/bin/sh", ["-c", command]);
|
||||
var stdout = "";
|
||||
var stderr = "";
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.addListener("data", function (chunk) { stdout += chunk; });
|
||||
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stderr.addListener("data", function (chunk) { stderr += chunk; });
|
||||
|
||||
child.addListener("exit", function (code) {
|
||||
if (code == 0) {
|
||||
if (callback) callback(null, stdout, stderr);
|
||||
} else {
|
||||
var e = new Error("Command failed: " + stderr);
|
||||
e.code = code;
|
||||
if (callback) callback(e, stdout, stderr);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function ChildProcess () {
|
||||
process.EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
var gotCHLD = false;
|
||||
var exitCode;
|
||||
var internal = this._internal = new InternalChildProcess();
|
||||
|
||||
var stdin = this.stdin = new Stream();
|
||||
var stdout = this.stdout = new Stream();
|
||||
var stderr = this.stderr = new Stream();
|
||||
|
||||
stderr.onend = stdout.onend = function () {
|
||||
if (gotCHLD && !stdout.readable && !stderr.readable) {
|
||||
self.emit('exit', exitCode);
|
||||
}
|
||||
};
|
||||
|
||||
internal.onexit = function (code) {
|
||||
gotCHLD = true;
|
||||
exitCode = code;
|
||||
if (!stdout.readable && !stderr.readable) {
|
||||
self.emit('exit', exitCode);
|
||||
}
|
||||
};
|
||||
|
||||
this.__defineGetter__('pid', function () { return internal.pid; });
|
||||
}
|
||||
inherits(ChildProcess, EventEmitter);
|
||||
|
||||
|
||||
ChildProcess.prototype.kill = function (sig) {
|
||||
return this._internal.kill(sig);
|
||||
};
|
||||
|
||||
|
||||
ChildProcess.prototype.spawn = function (path, args, env) {
|
||||
args = args || [];
|
||||
env = env || process.env;
|
||||
var envPairs = [];
|
||||
for (var key in env) {
|
||||
if (env.hasOwnProperty(key)) {
|
||||
envPairs.push(key + "=" + env[key]);
|
||||
}
|
||||
}
|
||||
|
||||
var fds = this._internal.spawn(path, args, envPairs);
|
||||
|
||||
this.stdin.open(fds[0]);
|
||||
this.stdin.writable = true;
|
||||
this.stdin.readable = false;
|
||||
|
||||
this.stdout.open(fds[1]);
|
||||
this.stdout.writable = false;
|
||||
this.stdout.readable = true;
|
||||
this.stdout.resume();
|
||||
|
||||
this.stderr.open(fds[2]);
|
||||
this.stderr.writable = false;
|
||||
this.stderr.readable = true;
|
||||
this.stderr.resume();
|
||||
};
|
||||
|
409
lib/http.js
409
lib/http.js
@ -1,11 +1,122 @@
|
||||
var debugLevel = 0;
|
||||
if ("NODE_DEBUG" in process.env) debugLevel = 1;
|
||||
|
||||
function debug (x) {
|
||||
if (debugLevel > 0) {
|
||||
process.binding('stdio').writeError(x + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
var sys = require('sys');
|
||||
var net = require('net');
|
||||
var events = require('events');
|
||||
|
||||
// FIXME: The TCP binding isn't actually used here, but it needs to be
|
||||
// loaded before the http binding.
|
||||
process.binding('tcp');
|
||||
var HTTPParser = process.binding('http_parser').HTTPParser;
|
||||
|
||||
var parserFreeList = [];
|
||||
|
||||
function newParser (type) {
|
||||
var parser;
|
||||
if (parserFreeList.length) {
|
||||
parser = parserFreeList.shift();
|
||||
parser.reinitialize(type);
|
||||
} else {
|
||||
parser = new HTTPParser(type);
|
||||
|
||||
parser.onMessageBegin = function () {
|
||||
parser.incoming = new IncomingMessage(parser.socket);
|
||||
parser.field = null;
|
||||
parser.value = null;
|
||||
};
|
||||
|
||||
// Only servers will get URL events.
|
||||
parser.onURL = function (b, start, len) {
|
||||
var slice = b.asciiSlice(start, start+len);
|
||||
if (parser.incoming.url) {
|
||||
parser.incoming.url += slice;
|
||||
} else {
|
||||
// Almost always will branch here.
|
||||
parser.incoming.url = slice;
|
||||
}
|
||||
};
|
||||
|
||||
parser.onHeaderField = function (b, start, len) {
|
||||
var slice = b.asciiSlice(start, start+len).toLowerCase();
|
||||
if (parser.value) {
|
||||
parser.incoming._addHeaderLine(parser.field, parser.value);
|
||||
parser.field = null;
|
||||
parser.value = null;
|
||||
}
|
||||
if (parser.field) {
|
||||
parser.field += slice;
|
||||
} else {
|
||||
parser.field = slice;
|
||||
}
|
||||
};
|
||||
|
||||
parser.onHeaderValue = function (b, start, len) {
|
||||
var slice = b.asciiSlice(start, start+len);
|
||||
if (parser.value) {
|
||||
parser.value += slice;
|
||||
} else {
|
||||
parser.value = slice;
|
||||
}
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = function (info) {
|
||||
if (parser.field && parser.value) {
|
||||
parser.incoming._addHeaderLine(parser.field, parser.value);
|
||||
}
|
||||
|
||||
parser.incoming.httpVersionMajor = info.versionMajor;
|
||||
parser.incoming.httpVersionMinor = info.versionMinor;
|
||||
|
||||
if (info.method) {
|
||||
// server only
|
||||
parser.incoming.method = info.method;
|
||||
} else {
|
||||
// client only
|
||||
parser.incoming.statusCode = info.statusCode;
|
||||
}
|
||||
|
||||
parser.onIncoming(parser.incoming, info.shouldKeepAlive);
|
||||
};
|
||||
|
||||
parser.onBody = function (b, start, len) {
|
||||
// TODO body encoding?
|
||||
var enc = parser.incoming._encoding;
|
||||
if (!enc) {
|
||||
parser.incoming.emit('data', b.slice(start, start+len));
|
||||
} else {
|
||||
var string;
|
||||
switch (enc) {
|
||||
case 'utf8':
|
||||
string = b.utf8Slice(start, start+len);
|
||||
break;
|
||||
case 'ascii':
|
||||
string = b.asciiSlice(start, start+len);
|
||||
break;
|
||||
case 'binary':
|
||||
string = b.binarySlice(start, start+len);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported encoding ' + enc + '. Use Buffer');
|
||||
}
|
||||
parser.incoming.emit('data', string);
|
||||
}
|
||||
};
|
||||
|
||||
parser.onMessageComplete = function () {
|
||||
parser.incoming.emit("end");
|
||||
};
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
function freeParser (parser) {
|
||||
if (parserFreeList.length < 1000) parserFreeList.push(parser);
|
||||
}
|
||||
|
||||
var http = process.binding('http');
|
||||
|
||||
var CRLF = "\r\n";
|
||||
var STATUS_CODES = exports.STATUS_CODES = {
|
||||
@ -56,10 +167,10 @@ var content_length_expression = /Content-Length/i;
|
||||
|
||||
|
||||
/* Abstract base class for ServerRequest and ClientResponse. */
|
||||
function IncomingMessage (connection) {
|
||||
function IncomingMessage (socket) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.connection = connection;
|
||||
this.socket = socket;
|
||||
this.httpVersion = null;
|
||||
this.headers = {};
|
||||
|
||||
@ -70,7 +181,7 @@ function IncomingMessage (connection) {
|
||||
|
||||
// response (client) only
|
||||
this.statusCode = null;
|
||||
this.client = this.connection;
|
||||
this.client = this.socket;
|
||||
}
|
||||
sys.inherits(IncomingMessage, events.EventEmitter);
|
||||
exports.IncomingMessage = IncomingMessage;
|
||||
@ -80,16 +191,21 @@ IncomingMessage.prototype._parseQueryString = function () {
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.setBodyEncoding = function (enc) {
|
||||
// TODO: Find a cleaner way of doing this.
|
||||
this.connection.setEncoding(enc);
|
||||
// TODO deprecation message?
|
||||
this.setEncoding(enc);
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.setEncoding = function (enc) {
|
||||
// TODO check values, error out on bad, and deprecation message?
|
||||
this._encoding = enc.toLowerCase();
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.pause = function () {
|
||||
this.connection.pause();
|
||||
this.socket.pause();
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.resume = function () {
|
||||
this.connection.resume();
|
||||
this.socket.resume();
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._addHeaderLine = function (field, value) {
|
||||
@ -102,10 +218,10 @@ IncomingMessage.prototype._addHeaderLine = function (field, value) {
|
||||
}
|
||||
};
|
||||
|
||||
function OutgoingMessage (connection) {
|
||||
events.EventEmitter.call(this, connection);
|
||||
function OutgoingMessage (socket) {
|
||||
events.EventEmitter.call(this, socket);
|
||||
|
||||
this.connection = connection;
|
||||
this.socket = socket;
|
||||
|
||||
this.output = [];
|
||||
this.outputEncodings = [];
|
||||
@ -126,7 +242,7 @@ exports.OutgoingMessage = OutgoingMessage;
|
||||
OutgoingMessage.prototype._send = function (data, encoding) {
|
||||
var length = this.output.length;
|
||||
|
||||
if (length === 0) {
|
||||
if (length === 0 || typeof data != 'string') {
|
||||
this.output.push(data);
|
||||
encoding = encoding || "ascii";
|
||||
this.outputEncodings.push(encoding);
|
||||
@ -138,11 +254,7 @@ OutgoingMessage.prototype._send = function (data, encoding) {
|
||||
|
||||
if ((lastEncoding === encoding) ||
|
||||
(!encoding && data.constructor === lastData.constructor)) {
|
||||
if (lastData.constructor === String) {
|
||||
this.output[length-1] = lastData + data;
|
||||
} else {
|
||||
this.output[length-1] = lastData.concat(data);
|
||||
}
|
||||
this.output[length-1] = lastData + data;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -228,7 +340,11 @@ OutgoingMessage.prototype.write = function (chunk, encoding) {
|
||||
|
||||
encoding = encoding || "ascii";
|
||||
if (this.chunked_encoding) {
|
||||
this._send(process._byteLength(chunk, encoding).toString(16));
|
||||
if (typeof chunk == 'string') {
|
||||
this._send(process._byteLength(chunk, encoding).toString(16));
|
||||
} else {
|
||||
this._send(chunk.length.toString(16));
|
||||
}
|
||||
this._send(CRLF);
|
||||
this._send(chunk, encoding);
|
||||
this._send(CRLF);
|
||||
@ -259,7 +375,7 @@ OutgoingMessage.prototype.close = function () {
|
||||
|
||||
|
||||
function ServerResponse (req) {
|
||||
OutgoingMessage.call(this, req.connection);
|
||||
OutgoingMessage.call(this, req.socket);
|
||||
|
||||
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
|
||||
this.use_chunked_encoding_by_default = false;
|
||||
@ -297,8 +413,8 @@ ServerResponse.prototype.writeHead = function (statusCode) {
|
||||
ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead;
|
||||
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
|
||||
|
||||
function ClientRequest (connection, method, url, headers) {
|
||||
OutgoingMessage.call(this, connection);
|
||||
function ClientRequest (socket, method, url, headers) {
|
||||
OutgoingMessage.call(this, socket);
|
||||
|
||||
this.should_keep_alive = false;
|
||||
if (method === "GET" || method === "HEAD") {
|
||||
@ -330,85 +446,19 @@ ClientRequest.prototype.close = function () {
|
||||
};
|
||||
|
||||
|
||||
function createIncomingMessageStream (connection, incoming_listener) {
|
||||
var incoming, field, value;
|
||||
|
||||
connection.addListener("messageBegin", function () {
|
||||
incoming = new IncomingMessage(connection);
|
||||
field = null;
|
||||
value = null;
|
||||
});
|
||||
|
||||
// Only servers will get URL events.
|
||||
connection.addListener("url", function (data) {
|
||||
incoming.url += data;
|
||||
});
|
||||
|
||||
connection.addListener("headerField", function (data) {
|
||||
if (value) {
|
||||
incoming._addHeaderLine(field, value);
|
||||
field = null;
|
||||
value = null;
|
||||
}
|
||||
if (field) {
|
||||
field += data;
|
||||
} else {
|
||||
field = data;
|
||||
}
|
||||
});
|
||||
|
||||
connection.addListener("headerValue", function (data) {
|
||||
if (value) {
|
||||
value += data;
|
||||
} else {
|
||||
value = data;
|
||||
}
|
||||
});
|
||||
|
||||
connection.addListener("headerComplete", function (info) {
|
||||
if (field && value) {
|
||||
incoming._addHeaderLine(field, value);
|
||||
}
|
||||
|
||||
incoming.httpVersion = info.httpVersion;
|
||||
incoming.httpVersionMajor = info.versionMajor;
|
||||
incoming.httpVersionMinor = info.versionMinor;
|
||||
|
||||
if (info.method) {
|
||||
// server only
|
||||
incoming.method = info.method;
|
||||
} else {
|
||||
// client only
|
||||
incoming.statusCode = info.statusCode;
|
||||
}
|
||||
|
||||
incoming_listener(incoming, info.should_keep_alive);
|
||||
});
|
||||
|
||||
connection.addListener("body", function (chunk) {
|
||||
incoming.emit('data', chunk);
|
||||
});
|
||||
|
||||
connection.addListener("messageComplete", function () {
|
||||
incoming.emit('end');
|
||||
});
|
||||
}
|
||||
|
||||
/* Returns true if the message queue is finished and the connection
|
||||
/* Returns true if the message queue is finished and the socket
|
||||
* should be closed. */
|
||||
function flushMessageQueue (connection, queue) {
|
||||
function flushMessageQueue (socket, queue) {
|
||||
while (queue[0]) {
|
||||
var message = queue[0];
|
||||
|
||||
while (message.output.length > 0) {
|
||||
if (connection.readyState !== "open" && connection.readyState !== "writeOnly") {
|
||||
return true;
|
||||
}
|
||||
if (!socket.writable) return true;
|
||||
|
||||
var data = message.output.shift();
|
||||
var encoding = message.outputEncodings.shift();
|
||||
|
||||
connection.write(data, encoding);
|
||||
socket.write(data, encoding);
|
||||
}
|
||||
|
||||
if (!message.finished) break;
|
||||
@ -422,152 +472,173 @@ function flushMessageQueue (connection, queue) {
|
||||
}
|
||||
|
||||
|
||||
exports.createServer = function (requestListener, options) {
|
||||
var server = new http.Server();
|
||||
//server.setOptions(options);
|
||||
server.addListener("request", requestListener);
|
||||
server.addListener("connection", connectionListener);
|
||||
return server;
|
||||
function Server (requestListener) {
|
||||
net.Server.call(this);
|
||||
this.addListener("request", requestListener);
|
||||
this.addListener("connection", connectionListener);
|
||||
}
|
||||
sys.inherits(Server, net.Server);
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
exports.createServer = function (requestListener) {
|
||||
return new Server(requestListener);
|
||||
};
|
||||
|
||||
function connectionListener (connection) {
|
||||
// An array of responses for each connection. In pipelined connections
|
||||
function connectionListener (socket) {
|
||||
var self = this;
|
||||
// An array of responses for each socket. In pipelined connections
|
||||
// we need to keep track of the order they were sent.
|
||||
var responses = [];
|
||||
|
||||
connection.resetParser();
|
||||
var parser = newParser('request');
|
||||
|
||||
socket.ondata = function (d, start, end) {
|
||||
parser.execute(d, start, end - start);
|
||||
};
|
||||
|
||||
socket.onend = function () {
|
||||
parser.finish();
|
||||
// unref the parser for easy gc
|
||||
freeParser(parser);
|
||||
|
||||
// is this really needed?
|
||||
connection.addListener("end", function () {
|
||||
if (responses.length == 0) {
|
||||
connection.close();
|
||||
socket.close();
|
||||
} else {
|
||||
responses[responses.length-1].closeOnFinish = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
createIncomingMessageStream(connection, function (incoming, should_keep_alive) {
|
||||
parser.socket = socket;
|
||||
// The following callback is issued after the headers have been read on a
|
||||
// new message. In this callback we setup the response object and pass it
|
||||
// to the user.
|
||||
parser.onIncoming = function (incoming, shouldKeepAlive) {
|
||||
var req = incoming;
|
||||
|
||||
var res = new ServerResponse(req);
|
||||
res.should_keep_alive = should_keep_alive;
|
||||
res.addListener("flush", function () {
|
||||
if (flushMessageQueue(connection, responses)) {
|
||||
connection.close();
|
||||
|
||||
res.shouldKeepAlive = shouldKeepAlive;
|
||||
res.addListener('flush', function () {
|
||||
if (flushMessageQueue(socket, responses)) {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
responses.push(res);
|
||||
|
||||
connection.server.emit("request", req, res);
|
||||
});
|
||||
self.emit('request', req, res);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
exports.createClient = function (port, host) {
|
||||
var client = new http.Client();
|
||||
var secure_credentials={ secure : false };
|
||||
function Client ( ) {
|
||||
net.Stream.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
var requests = [];
|
||||
var currentRequest;
|
||||
|
||||
client.tcpSetSecure = client.setSecure;
|
||||
client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) {
|
||||
secure_credentials.secure = true;
|
||||
secure_credentials.format_type = format_type;
|
||||
secure_credentials.ca_certs = ca_certs;
|
||||
secure_credentials.crl_list = crl_list;
|
||||
secure_credentials.private_key = private_key;
|
||||
secure_credentials.certificate = certificate;
|
||||
}
|
||||
var parser = newParser('response');
|
||||
parser.socket = this;
|
||||
|
||||
client._reconnect = function () {
|
||||
if (client.readyState != "opening") {
|
||||
//sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState);
|
||||
client.connect(port, host);
|
||||
if (secure_credentials.secure) {
|
||||
client.tcpSetSecure(secure_credentials.format_type,
|
||||
secure_credentials.ca_certs,
|
||||
secure_credentials.crl_list,
|
||||
secure_credentials.private_key,
|
||||
secure_credentials.certificate);
|
||||
}
|
||||
self._reconnect = function () {
|
||||
if (self.readyState != "opening") {
|
||||
debug("HTTP CLIENT: reconnecting readyState = " + self.readyState);
|
||||
self.connect(self.port, self.host);
|
||||
}
|
||||
};
|
||||
|
||||
client._pushRequest = function (req) {
|
||||
self._pushRequest = function (req) {
|
||||
req.addListener("flush", function () {
|
||||
if (client.readyState == "closed") {
|
||||
//sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState);
|
||||
client._reconnect();
|
||||
if (self.readyState == "closed") {
|
||||
debug("HTTP CLIENT request flush. reconnect. readyState = " + self.readyState);
|
||||
self._reconnect();
|
||||
return;
|
||||
}
|
||||
//sys.debug("client flush readyState = " + client.readyState);
|
||||
if (req == currentRequest) flushMessageQueue(client, [req]);
|
||||
|
||||
debug("self flush readyState = " + self.readyState);
|
||||
if (req == currentRequest) flushMessageQueue(self, [req]);
|
||||
});
|
||||
requests.push(req);
|
||||
};
|
||||
|
||||
client.addListener("connect", function () {
|
||||
client.resetParser();
|
||||
currentRequest = requests.shift();
|
||||
this.ondata = function (d, start, end) {
|
||||
parser.execute(d, start, end - start);
|
||||
};
|
||||
|
||||
self.addListener("connect", function () {
|
||||
parser.reinitialize('response');
|
||||
sys.puts('requests: ' + sys.inspect(requests));
|
||||
currentRequest = requests.shift()
|
||||
currentRequest.flush();
|
||||
});
|
||||
|
||||
client.addListener("end", function () {
|
||||
//sys.debug("client got end closing. readyState = " + client.readyState);
|
||||
client.close();
|
||||
self.addListener("end", function () {
|
||||
parser.finish();
|
||||
freeParser(parser);
|
||||
|
||||
debug("self got end closing. readyState = " + self.readyState);
|
||||
self.close();
|
||||
});
|
||||
|
||||
client.addListener("close", function (had_error) {
|
||||
self.addListener("close", function (had_error) {
|
||||
if (had_error) {
|
||||
client.emit("error");
|
||||
self.emit("error");
|
||||
return;
|
||||
}
|
||||
|
||||
//sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState);
|
||||
debug("HTTP CLIENT onClose. readyState = " + self.readyState);
|
||||
|
||||
// If there are more requests to handle, reconnect.
|
||||
if (requests.length > 0) {
|
||||
client._reconnect();
|
||||
self._reconnect();
|
||||
}
|
||||
});
|
||||
|
||||
createIncomingMessageStream(client, function (res) {
|
||||
//sys.debug("incoming response!");
|
||||
parser.onIncoming = function (res) {
|
||||
debug("incoming response!");
|
||||
|
||||
res.addListener('end', function ( ) {
|
||||
//sys.debug("request complete disconnecting. readyState = " + client.readyState);
|
||||
client.close();
|
||||
debug("request complete disconnecting. readyState = " + self.readyState);
|
||||
self.close();
|
||||
});
|
||||
|
||||
currentRequest.emit("response", res);
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
};
|
||||
sys.inherits(Client, net.Stream);
|
||||
|
||||
http.Client.prototype.get = function () {
|
||||
exports.Client = Client;
|
||||
|
||||
exports.createClient = function (port, host) {
|
||||
var c = new Client;
|
||||
c.port = port;
|
||||
c.host = host;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
Client.prototype.get = function () {
|
||||
throw new Error("client.get(...) is now client.request('GET', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.head = function () {
|
||||
Client.prototype.head = function () {
|
||||
throw new Error("client.head(...) is now client.request('HEAD', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.post = function () {
|
||||
Client.prototype.post = function () {
|
||||
throw new Error("client.post(...) is now client.request('POST', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.del = function () {
|
||||
Client.prototype.del = function () {
|
||||
throw new Error("client.del(...) is now client.request('DELETE', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.put = function () {
|
||||
Client.prototype.put = function () {
|
||||
throw new Error("client.put(...) is now client.request('PUT', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.request = function (method, url, headers) {
|
||||
Client.prototype.request = function (method, url, headers) {
|
||||
if (typeof(url) != "string") { // assume method was omitted, shift arguments
|
||||
headers = url;
|
||||
url = method;
|
||||
@ -580,7 +651,7 @@ http.Client.prototype.request = function (method, url, headers) {
|
||||
|
||||
|
||||
exports.cat = function (url, encoding_, headers_) {
|
||||
var encoding = 'utf8',
|
||||
var encoding = 'utf8',
|
||||
headers = {},
|
||||
callback = null;
|
||||
|
||||
|
641
lib/http_old.js
Normal file
641
lib/http_old.js
Normal file
@ -0,0 +1,641 @@
|
||||
var sys = require('sys');
|
||||
var events = require('events');
|
||||
|
||||
// FIXME: The TCP binding isn't actually used here, but it needs to be
|
||||
// loaded before the http binding.
|
||||
process.binding('tcp');
|
||||
|
||||
var http = process.binding('http');
|
||||
|
||||
var CRLF = "\r\n";
|
||||
var STATUS_CODES = exports.STATUS_CODES = {
|
||||
100 : 'Continue',
|
||||
101 : 'Switching Protocols',
|
||||
200 : 'OK',
|
||||
201 : 'Created',
|
||||
202 : 'Accepted',
|
||||
203 : 'Non-Authoritative Information',
|
||||
204 : 'No Content',
|
||||
205 : 'Reset Content',
|
||||
206 : 'Partial Content',
|
||||
300 : 'Multiple Choices',
|
||||
301 : 'Moved Permanently',
|
||||
302 : 'Moved Temporarily',
|
||||
303 : 'See Other',
|
||||
304 : 'Not Modified',
|
||||
305 : 'Use Proxy',
|
||||
400 : 'Bad Request',
|
||||
401 : 'Unauthorized',
|
||||
402 : 'Payment Required',
|
||||
403 : 'Forbidden',
|
||||
404 : 'Not Found',
|
||||
405 : 'Method Not Allowed',
|
||||
406 : 'Not Acceptable',
|
||||
407 : 'Proxy Authentication Required',
|
||||
408 : 'Request Time-out',
|
||||
409 : 'Conflict',
|
||||
410 : 'Gone',
|
||||
411 : 'Length Required',
|
||||
412 : 'Precondition Failed',
|
||||
413 : 'Request Entity Too Large',
|
||||
414 : 'Request-URI Too Large',
|
||||
415 : 'Unsupported Media Type',
|
||||
500 : 'Internal Server Error',
|
||||
501 : 'Not Implemented',
|
||||
502 : 'Bad Gateway',
|
||||
503 : 'Service Unavailable',
|
||||
504 : 'Gateway Time-out',
|
||||
505 : 'HTTP Version not supported'
|
||||
};
|
||||
|
||||
var connection_expression = /Connection/i;
|
||||
var transfer_encoding_expression = /Transfer-Encoding/i;
|
||||
var close_expression = /close/i;
|
||||
var chunk_expression = /chunk/i;
|
||||
var content_length_expression = /Content-Length/i;
|
||||
|
||||
|
||||
/* Abstract base class for ServerRequest and ClientResponse. */
|
||||
function IncomingMessage (connection) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.connection = connection;
|
||||
this.httpVersion = null;
|
||||
this.headers = {};
|
||||
|
||||
// request (server) only
|
||||
this.url = "";
|
||||
|
||||
this.method = null;
|
||||
|
||||
// response (client) only
|
||||
this.statusCode = null;
|
||||
this.client = this.connection;
|
||||
}
|
||||
sys.inherits(IncomingMessage, events.EventEmitter);
|
||||
exports.IncomingMessage = IncomingMessage;
|
||||
|
||||
IncomingMessage.prototype._parseQueryString = function () {
|
||||
throw new Error("_parseQueryString is deprecated. Use require(\"querystring\") to parse query strings.\n");
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.setBodyEncoding = function (enc) {
|
||||
// TODO: Find a cleaner way of doing this.
|
||||
this.connection.setEncoding(enc);
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.pause = function () {
|
||||
this.connection.pause();
|
||||
};
|
||||
|
||||
IncomingMessage.prototype.resume = function () {
|
||||
this.connection.resume();
|
||||
};
|
||||
|
||||
IncomingMessage.prototype._addHeaderLine = function (field, value) {
|
||||
if (field in this.headers) {
|
||||
// TODO Certain headers like 'Content-Type' should not be concatinated.
|
||||
// See https://www.google.com/reader/view/?tab=my#overview-page
|
||||
this.headers[field] += ", " + value;
|
||||
} else {
|
||||
this.headers[field] = value;
|
||||
}
|
||||
};
|
||||
|
||||
function OutgoingMessage (connection) {
|
||||
events.EventEmitter.call(this, connection);
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
this.output = [];
|
||||
this.outputEncodings = [];
|
||||
|
||||
this.closeOnFinish = false;
|
||||
this.chunked_encoding = false;
|
||||
this.should_keep_alive = true;
|
||||
this.use_chunked_encoding_by_default = true;
|
||||
|
||||
this.flushing = false;
|
||||
this.headWritten = false;
|
||||
|
||||
this.finished = false;
|
||||
}
|
||||
sys.inherits(OutgoingMessage, events.EventEmitter);
|
||||
exports.OutgoingMessage = OutgoingMessage;
|
||||
|
||||
OutgoingMessage.prototype._send = function (data, encoding) {
|
||||
var length = this.output.length;
|
||||
|
||||
if (length === 0) {
|
||||
this.output.push(data);
|
||||
encoding = encoding || "ascii";
|
||||
this.outputEncodings.push(encoding);
|
||||
return;
|
||||
}
|
||||
|
||||
var lastEncoding = this.outputEncodings[length-1];
|
||||
var lastData = this.output[length-1];
|
||||
|
||||
if ((lastEncoding === encoding) ||
|
||||
(!encoding && data.constructor === lastData.constructor)) {
|
||||
if (lastData.constructor === String) {
|
||||
this.output[length-1] = lastData + data;
|
||||
} else {
|
||||
this.output[length-1] = lastData.concat(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.output.push(data);
|
||||
encoding = encoding || "ascii";
|
||||
this.outputEncodings.push(encoding);
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) {
|
||||
var sent_connection_header = false;
|
||||
var sent_content_length_header = false;
|
||||
var sent_transfer_encoding_header = false;
|
||||
|
||||
// first_line in the case of request is: "GET /index.html HTTP/1.1\r\n"
|
||||
// in the case of response it is: "HTTP/1.1 200 OK\r\n"
|
||||
var message_header = first_line;
|
||||
var field, value;
|
||||
for (var i in headers) {
|
||||
if (headers[i] instanceof Array) {
|
||||
field = headers[i][0];
|
||||
value = headers[i][1];
|
||||
} else {
|
||||
if (!headers.hasOwnProperty(i)) continue;
|
||||
field = i;
|
||||
value = headers[i];
|
||||
}
|
||||
|
||||
message_header += field + ": " + value + CRLF;
|
||||
|
||||
if (connection_expression.test(field)) {
|
||||
sent_connection_header = true;
|
||||
if (close_expression.test(value)) this.closeOnFinish = true;
|
||||
|
||||
} else if (transfer_encoding_expression.test(field)) {
|
||||
sent_transfer_encoding_header = true;
|
||||
if (chunk_expression.test(value)) this.chunked_encoding = true;
|
||||
|
||||
} else if (content_length_expression.test(field)) {
|
||||
sent_content_length_header = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// keep-alive logic
|
||||
if (sent_connection_header == false) {
|
||||
if (this.should_keep_alive &&
|
||||
(sent_content_length_header || this.use_chunked_encoding_by_default)) {
|
||||
message_header += "Connection: keep-alive\r\n";
|
||||
} else {
|
||||
this.closeOnFinish = true;
|
||||
message_header += "Connection: close\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
|
||||
if (this.use_chunked_encoding_by_default) {
|
||||
message_header += "Transfer-Encoding: chunked\r\n";
|
||||
this.chunked_encoding = true;
|
||||
}
|
||||
else {
|
||||
this.closeOnFinish = true;
|
||||
}
|
||||
}
|
||||
|
||||
message_header += CRLF;
|
||||
|
||||
this._send(message_header);
|
||||
// wait until the first body chunk, or close(), is sent to flush.
|
||||
};
|
||||
|
||||
|
||||
OutgoingMessage.prototype.sendBody = function () {
|
||||
throw new Error("sendBody() has been renamed to write(). " +
|
||||
"The 'body' event has been renamed to 'data' and " +
|
||||
"the 'complete' event has been renamed to 'end'.");
|
||||
};
|
||||
|
||||
|
||||
OutgoingMessage.prototype.write = function (chunk, encoding) {
|
||||
if ( (this instanceof ServerResponse) && !this.headWritten) {
|
||||
throw new Error("writeHead() must be called before write()")
|
||||
}
|
||||
|
||||
encoding = encoding || "ascii";
|
||||
if (this.chunked_encoding) {
|
||||
this._send(process._byteLength(chunk, encoding).toString(16));
|
||||
this._send(CRLF);
|
||||
this._send(chunk, encoding);
|
||||
this._send(CRLF);
|
||||
} else {
|
||||
this._send(chunk, encoding);
|
||||
}
|
||||
|
||||
if (this.flushing) {
|
||||
this.flush();
|
||||
} else {
|
||||
this.flushing = true;
|
||||
}
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.flush = function () {
|
||||
this.emit("flush");
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.finish = function () {
|
||||
throw new Error("finish() has been renamed to close().");
|
||||
};
|
||||
|
||||
OutgoingMessage.prototype.close = function () {
|
||||
if (this.chunked_encoding) this._send("0\r\n\r\n"); // last chunk
|
||||
this.finished = true;
|
||||
this.flush();
|
||||
};
|
||||
|
||||
|
||||
function ServerResponse (req) {
|
||||
OutgoingMessage.call(this, req.connection);
|
||||
|
||||
if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
|
||||
this.use_chunked_encoding_by_default = false;
|
||||
this.should_keep_alive = false;
|
||||
}
|
||||
}
|
||||
sys.inherits(ServerResponse, OutgoingMessage);
|
||||
exports.ServerResponse = ServerResponse;
|
||||
|
||||
|
||||
ServerResponse.prototype.writeHead = function (statusCode) {
|
||||
var reasonPhrase, headers, headerIndex;
|
||||
|
||||
if (typeof arguments[1] == 'string') {
|
||||
reasonPhrase = arguments[1];
|
||||
headerIndex = 2;
|
||||
} else {
|
||||
reasonPhrase = STATUS_CODES[statusCode] || "unknown";
|
||||
headerIndex = 1;
|
||||
}
|
||||
|
||||
if (typeof arguments[headerIndex] == 'object') {
|
||||
headers = arguments[headerIndex];
|
||||
} else {
|
||||
headers = {};
|
||||
}
|
||||
|
||||
var status_line = "HTTP/1.1 " + statusCode.toString() + " "
|
||||
+ reasonPhrase + CRLF;
|
||||
this.sendHeaderLines(status_line, headers);
|
||||
this.headWritten = true;
|
||||
};
|
||||
|
||||
// TODO eventually remove sendHeader(), writeHeader()
|
||||
ServerResponse.prototype.sendHeader = ServerResponse.prototype.writeHead;
|
||||
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
|
||||
|
||||
function ClientRequest (connection, method, url, headers) {
|
||||
OutgoingMessage.call(this, connection);
|
||||
|
||||
this.should_keep_alive = false;
|
||||
if (method === "GET" || method === "HEAD") {
|
||||
this.use_chunked_encoding_by_default = false;
|
||||
} else {
|
||||
this.use_chunked_encoding_by_default = true;
|
||||
}
|
||||
this.closeOnFinish = true;
|
||||
|
||||
this.sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers);
|
||||
}
|
||||
sys.inherits(ClientRequest, OutgoingMessage);
|
||||
exports.ClientRequest = ClientRequest;
|
||||
|
||||
ClientRequest.prototype.finish = function () {
|
||||
throw new Error( "finish() has been renamed to close() and no longer takes "
|
||||
+ "a response handler as an argument. Manually add a 'response' listener "
|
||||
+ "to the request object."
|
||||
);
|
||||
};
|
||||
|
||||
ClientRequest.prototype.close = function () {
|
||||
if (arguments.length > 0) {
|
||||
throw new Error( "ClientRequest.prototype.close does not take any arguments. "
|
||||
+ "Add a response listener manually to the request object."
|
||||
);
|
||||
}
|
||||
OutgoingMessage.prototype.close.call(this);
|
||||
};
|
||||
|
||||
|
||||
function createIncomingMessageStream (connection, incoming_listener) {
|
||||
var incoming, field, value;
|
||||
|
||||
connection.addListener("messageBegin", function () {
|
||||
incoming = new IncomingMessage(connection);
|
||||
field = null;
|
||||
value = null;
|
||||
});
|
||||
|
||||
// Only servers will get URL events.
|
||||
connection.addListener("url", function (data) {
|
||||
incoming.url += data;
|
||||
});
|
||||
|
||||
connection.addListener("headerField", function (data) {
|
||||
if (value) {
|
||||
incoming._addHeaderLine(field, value);
|
||||
field = null;
|
||||
value = null;
|
||||
}
|
||||
if (field) {
|
||||
field += data;
|
||||
} else {
|
||||
field = data;
|
||||
}
|
||||
});
|
||||
|
||||
connection.addListener("headerValue", function (data) {
|
||||
if (value) {
|
||||
value += data;
|
||||
} else {
|
||||
value = data;
|
||||
}
|
||||
});
|
||||
|
||||
connection.addListener("headerComplete", function (info) {
|
||||
if (field && value) {
|
||||
incoming._addHeaderLine(field, value);
|
||||
}
|
||||
|
||||
incoming.httpVersion = info.httpVersion;
|
||||
incoming.httpVersionMajor = info.versionMajor;
|
||||
incoming.httpVersionMinor = info.versionMinor;
|
||||
|
||||
if (info.method) {
|
||||
// server only
|
||||
incoming.method = info.method;
|
||||
} else {
|
||||
// client only
|
||||
incoming.statusCode = info.statusCode;
|
||||
}
|
||||
|
||||
incoming_listener(incoming, info.should_keep_alive);
|
||||
});
|
||||
|
||||
connection.addListener("body", function (chunk) {
|
||||
incoming.emit('data', chunk);
|
||||
});
|
||||
|
||||
connection.addListener("messageComplete", function () {
|
||||
incoming.emit('end');
|
||||
});
|
||||
}
|
||||
|
||||
/* Returns true if the message queue is finished and the connection
|
||||
* should be closed. */
|
||||
function flushMessageQueue (connection, queue) {
|
||||
while (queue[0]) {
|
||||
var message = queue[0];
|
||||
|
||||
while (message.output.length > 0) {
|
||||
if (connection.readyState !== "open" && connection.readyState !== "writeOnly") {
|
||||
return true;
|
||||
}
|
||||
|
||||
var data = message.output.shift();
|
||||
var encoding = message.outputEncodings.shift();
|
||||
|
||||
connection.write(data, encoding);
|
||||
}
|
||||
|
||||
if (!message.finished) break;
|
||||
|
||||
message.emit("sent");
|
||||
queue.shift();
|
||||
|
||||
if (message.closeOnFinish) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
exports.createServer = function (requestListener, options) {
|
||||
var server = new http.Server();
|
||||
//server.setOptions(options);
|
||||
server.addListener("request", requestListener);
|
||||
server.addListener("connection", connectionListener);
|
||||
return server;
|
||||
};
|
||||
|
||||
function connectionListener (connection) {
|
||||
// An array of responses for each connection. In pipelined connections
|
||||
// we need to keep track of the order they were sent.
|
||||
var responses = [];
|
||||
|
||||
connection.resetParser();
|
||||
|
||||
// is this really needed?
|
||||
connection.addListener("end", function () {
|
||||
if (responses.length == 0) {
|
||||
connection.close();
|
||||
} else {
|
||||
responses[responses.length-1].closeOnFinish = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
createIncomingMessageStream(connection, function (incoming, should_keep_alive) {
|
||||
var req = incoming;
|
||||
|
||||
var res = new ServerResponse(req);
|
||||
res.should_keep_alive = should_keep_alive;
|
||||
res.addListener("flush", function () {
|
||||
if (flushMessageQueue(connection, responses)) {
|
||||
connection.close();
|
||||
}
|
||||
});
|
||||
responses.push(res);
|
||||
|
||||
connection.server.emit("request", req, res);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
exports.createClient = function (port, host) {
|
||||
var client = new http.Client();
|
||||
var secure_credentials={ secure : false };
|
||||
|
||||
var requests = [];
|
||||
var currentRequest;
|
||||
|
||||
client.tcpSetSecure = client.setSecure;
|
||||
client.setSecure = function(format_type, ca_certs, crl_list, private_key, certificate) {
|
||||
secure_credentials.secure = true;
|
||||
secure_credentials.format_type = format_type;
|
||||
secure_credentials.ca_certs = ca_certs;
|
||||
secure_credentials.crl_list = crl_list;
|
||||
secure_credentials.private_key = private_key;
|
||||
secure_credentials.certificate = certificate;
|
||||
}
|
||||
|
||||
client._reconnect = function () {
|
||||
if (client.readyState != "opening") {
|
||||
//sys.debug("HTTP CLIENT: reconnecting readyState = " + client.readyState);
|
||||
client.connect(port, host);
|
||||
if (secure_credentials.secure) {
|
||||
client.tcpSetSecure(secure_credentials.format_type,
|
||||
secure_credentials.ca_certs,
|
||||
secure_credentials.crl_list,
|
||||
secure_credentials.private_key,
|
||||
secure_credentials.certificate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client._pushRequest = function (req) {
|
||||
req.addListener("flush", function () {
|
||||
if (client.readyState == "closed") {
|
||||
//sys.debug("HTTP CLIENT request flush. reconnect. readyState = " + client.readyState);
|
||||
client._reconnect();
|
||||
return;
|
||||
}
|
||||
//sys.debug("client flush readyState = " + client.readyState);
|
||||
if (req == currentRequest) flushMessageQueue(client, [req]);
|
||||
});
|
||||
requests.push(req);
|
||||
};
|
||||
|
||||
client.addListener("connect", function () {
|
||||
client.resetParser();
|
||||
currentRequest = requests.shift();
|
||||
currentRequest.flush();
|
||||
});
|
||||
|
||||
client.addListener("end", function () {
|
||||
//sys.debug("client got end closing. readyState = " + client.readyState);
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.addListener("close", function (had_error) {
|
||||
if (had_error) {
|
||||
client.emit("error");
|
||||
return;
|
||||
}
|
||||
|
||||
//sys.debug("HTTP CLIENT onClose. readyState = " + client.readyState);
|
||||
|
||||
// If there are more requests to handle, reconnect.
|
||||
if (requests.length > 0) {
|
||||
client._reconnect();
|
||||
}
|
||||
});
|
||||
|
||||
createIncomingMessageStream(client, function (res) {
|
||||
//sys.debug("incoming response!");
|
||||
|
||||
res.addListener('end', function ( ) {
|
||||
//sys.debug("request complete disconnecting. readyState = " + client.readyState);
|
||||
client.close();
|
||||
});
|
||||
|
||||
currentRequest.emit("response", res);
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
http.Client.prototype.get = function () {
|
||||
throw new Error("client.get(...) is now client.request('GET', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.head = function () {
|
||||
throw new Error("client.head(...) is now client.request('HEAD', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.post = function () {
|
||||
throw new Error("client.post(...) is now client.request('POST', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.del = function () {
|
||||
throw new Error("client.del(...) is now client.request('DELETE', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.put = function () {
|
||||
throw new Error("client.put(...) is now client.request('PUT', ...)");
|
||||
};
|
||||
|
||||
http.Client.prototype.request = function (method, url, headers) {
|
||||
if (typeof(url) != "string") { // assume method was omitted, shift arguments
|
||||
headers = url;
|
||||
url = method;
|
||||
method = null;
|
||||
}
|
||||
var req = new ClientRequest(this, method || "GET", url, headers);
|
||||
this._pushRequest(req);
|
||||
return req;
|
||||
};
|
||||
|
||||
|
||||
exports.cat = function (url, encoding_, headers_) {
|
||||
var encoding = 'utf8',
|
||||
headers = {},
|
||||
callback = null;
|
||||
|
||||
// parse the arguments for the various options... very ugly
|
||||
if (typeof(arguments[1]) == 'string') {
|
||||
encoding = arguments[1];
|
||||
if (typeof(arguments[2]) == 'object') {
|
||||
headers = arguments[2];
|
||||
if (typeof(arguments[3]) == 'function') callback = arguments[3];
|
||||
} else {
|
||||
if (typeof(arguments[2]) == 'function') callback = arguments[2];
|
||||
}
|
||||
} else {
|
||||
// didn't specify encoding
|
||||
if (typeof(arguments[1]) == 'object') {
|
||||
headers = arguments[1];
|
||||
callback = arguments[2];
|
||||
} else {
|
||||
callback = arguments[1];
|
||||
}
|
||||
}
|
||||
|
||||
var url = require("url").parse(url);
|
||||
|
||||
var hasHost = false;
|
||||
for (var i in headers) {
|
||||
if (i.toLowerCase() === "host") {
|
||||
hasHost = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasHost) headers["Host"] = url.hostname;
|
||||
|
||||
var content = "";
|
||||
|
||||
var client = exports.createClient(url.port || 80, url.hostname);
|
||||
var req = client.request((url.pathname || "/")+(url.search || "")+(url.hash || ""), headers);
|
||||
|
||||
req.addListener('response', function (res) {
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
if (callback) callback(res.statusCode);
|
||||
client.close();
|
||||
return;
|
||||
}
|
||||
res.setBodyEncoding(encoding);
|
||||
res.addListener('data', function (chunk) { content += chunk; });
|
||||
res.addListener('end', function () {
|
||||
if (callback) callback(null, content);
|
||||
});
|
||||
});
|
||||
|
||||
client.addListener("error", function (err) {
|
||||
// todo an error should actually be passed here...
|
||||
if (callback) callback(new Error('Connection error'));
|
||||
});
|
||||
|
||||
req.close();
|
||||
};
|
959
lib/net.js
Normal file
959
lib/net.js
Normal file
@ -0,0 +1,959 @@
|
||||
var sys = require("sys");
|
||||
var fs = require("fs");
|
||||
var events = require("events");
|
||||
|
||||
var debugLevel = 0;
|
||||
if ('NODE_DEBUG' in process.ENV) debugLevel = 1;
|
||||
function debug () {
|
||||
if (debugLevel > 0) sys.error.apply(this, arguments);
|
||||
}
|
||||
|
||||
var binding = process.binding('net');
|
||||
|
||||
// Note about Buffer interface:
|
||||
// I'm attempting to do the simplest possible interface to abstracting raw
|
||||
// memory allocation. This might turn out to be too simple - it seems that
|
||||
// I always use a buffer.used member to keep track of how much I've filled.
|
||||
// Perhaps giving the Buffer a file-like interface with a head (which would
|
||||
// represent buffer.used) that can be seeked around would be easier. I'm not
|
||||
// yet convinced that every use-case can be fit into that abstraction, so
|
||||
// waiting to implement it until I get more experience with this.
|
||||
var Buffer = require('buffer').Buffer;
|
||||
|
||||
var IOWatcher = process.IOWatcher;
|
||||
var assert = process.assert;
|
||||
|
||||
var socket = binding.socket;
|
||||
var bind = binding.bind;
|
||||
var connect = binding.connect;
|
||||
var listen = binding.listen;
|
||||
var accept = binding.accept;
|
||||
var close = binding.close;
|
||||
var shutdown = binding.shutdown;
|
||||
var read = binding.read;
|
||||
var write = binding.write;
|
||||
var toRead = binding.toRead;
|
||||
var setNoDelay = binding.setNoDelay;
|
||||
var socketError = binding.socketError;
|
||||
var getsockname = binding.getsockname;
|
||||
var getaddrinfo = binding.getaddrinfo;
|
||||
var needsLookup = binding.needsLookup;
|
||||
var errnoException = binding.errnoException;
|
||||
var EINPROGRESS = binding.EINPROGRESS;
|
||||
var ENOENT = binding.ENOENT;
|
||||
var END_OF_FILE = 0;
|
||||
|
||||
|
||||
// IDLE TIMEOUTS
|
||||
//
|
||||
// Because often many sockets will have the same idle timeout we will not
|
||||
// use one timeout watcher per socket. It is too much overhead. Instead
|
||||
// we'll use a single watcher for all sockets with the same timeout value
|
||||
// and a linked list. This technique is described in the libev manual:
|
||||
// http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#Be_smart_about_timeouts
|
||||
|
||||
|
||||
var timeout = new (function () {
|
||||
// Object containing all lists, timers
|
||||
// key = time in milliseconds
|
||||
// value = list
|
||||
var lists = {};
|
||||
|
||||
// show the most idle socket
|
||||
function peek (list) {
|
||||
if (list._idlePrev == list) return null;
|
||||
return list._idlePrev;
|
||||
}
|
||||
|
||||
|
||||
// remove the most idle socket from the list
|
||||
function shift (list) {
|
||||
var first = list._idlePrev;
|
||||
remove(first);
|
||||
return first;
|
||||
}
|
||||
|
||||
|
||||
// remove a socket from its list
|
||||
function remove (socket) {
|
||||
socket._idleNext._idlePrev = socket._idlePrev;
|
||||
socket._idlePrev._idleNext = socket._idleNext;
|
||||
}
|
||||
|
||||
|
||||
// remove a socket from its list and place at the end.
|
||||
function append (list, socket) {
|
||||
remove(socket);
|
||||
socket._idleNext = list._idleNext;
|
||||
socket._idleNext._idlePrev = socket;
|
||||
socket._idlePrev = list
|
||||
list._idleNext = socket;
|
||||
}
|
||||
|
||||
|
||||
function normalize (msecs) {
|
||||
if (!msecs || msecs <= 0) return 0;
|
||||
// round up to one sec
|
||||
if (msecs < 1000) return 1000;
|
||||
// round down to nearest second.
|
||||
return msecs - (msecs % 1000);
|
||||
}
|
||||
|
||||
// the main function - creates lists on demand and the watchers associated
|
||||
// with them.
|
||||
function insert (socket, msecs) {
|
||||
socket._idleStart = process.now;
|
||||
socket._idleTimeout = msecs;
|
||||
|
||||
if (!msecs) return;
|
||||
|
||||
var list;
|
||||
|
||||
if (lists[msecs]) {
|
||||
list = lists[msecs];
|
||||
} else {
|
||||
list = new process.Timer();
|
||||
list._idleNext = list;
|
||||
list._idlePrev = list;
|
||||
|
||||
lists[msecs] = list;
|
||||
|
||||
list.callback = function () {
|
||||
sys.puts('timeout callback ' + msecs);
|
||||
// TODO - don't stop and start the watcher all the time.
|
||||
// just set its repeat
|
||||
var now = process.now;
|
||||
var first;
|
||||
while (first = peek(list)) {
|
||||
var diff = now - first._idleStart;
|
||||
if (diff < msecs) {
|
||||
list.again(msecs - diff);
|
||||
sys.puts(msecs + ' list wait');
|
||||
return;
|
||||
} else {
|
||||
remove(first);
|
||||
assert(first != peek(list));
|
||||
first.emit('timeout');
|
||||
first.forceClose(new Error('idle timeout'));
|
||||
}
|
||||
}
|
||||
sys.puts(msecs + ' list empty');
|
||||
assert(list._idleNext == list); // list is empty
|
||||
list.stop();
|
||||
};
|
||||
}
|
||||
|
||||
if (list._idleNext == list) {
|
||||
// if empty (re)start the timer
|
||||
list.again(msecs);
|
||||
}
|
||||
|
||||
append(list, socket);
|
||||
assert(list._idleNext != list); // list is not empty
|
||||
}
|
||||
|
||||
|
||||
var unenroll = this.unenroll = function (socket) {
|
||||
if (socket._idleNext) {
|
||||
socket._idleNext._idlePrev = socket._idlePrev;
|
||||
socket._idlePrev._idleNext = socket._idleNext;
|
||||
|
||||
var list = lists[socket._idleTimeout];
|
||||
// if empty then stop the watcher
|
||||
//sys.puts('unenroll');
|
||||
if (list && list._idlePrev == list) {
|
||||
//sys.puts('unenroll: list empty');
|
||||
list.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Does not start the time, just sets up the members needed.
|
||||
this.enroll = function (socket, msecs) {
|
||||
// if this socket was already in a list somewhere
|
||||
// then we should unenroll it from that
|
||||
if (socket._idleNext) unenroll(socket);
|
||||
|
||||
socket._idleTimeout = msecs;
|
||||
socket._idleNext = socket;
|
||||
socket._idlePrev = socket;
|
||||
};
|
||||
|
||||
// call this whenever the socket is active (not idle)
|
||||
// it will reset its timeout.
|
||||
this.active = function (socket) {
|
||||
var msecs = socket._idleTimeout;
|
||||
if (msecs) {
|
||||
var list = lists[msecs];
|
||||
if (socket._idleNext == socket) {
|
||||
insert(socket, msecs);
|
||||
} else {
|
||||
// inline append
|
||||
socket._idleStart = process.now;
|
||||
socket._idleNext._idlePrev = socket._idlePrev;
|
||||
socket._idlePrev._idleNext = socket._idleNext;
|
||||
socket._idleNext = list._idleNext;
|
||||
socket._idleNext._idlePrev = socket;
|
||||
socket._idlePrev = list
|
||||
list._idleNext = socket;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// This is a free list to avoid creating so many of the same object.
|
||||
|
||||
function FreeList (name, max, constructor) {
|
||||
this.name = name;
|
||||
this.constructor = constructor;
|
||||
this.max = max;
|
||||
this.list = [];
|
||||
}
|
||||
|
||||
|
||||
FreeList.prototype.alloc = function () {
|
||||
//debug("alloc " + this.name + " " + this.list.length);
|
||||
return this.list.length ? this.list.shift()
|
||||
: this.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
FreeList.prototype.free = function (obj) {
|
||||
//debug("free " + this.name + " " + this.list.length);
|
||||
if (this.list.length < this.max) {
|
||||
this.list.push(obj);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var ioWatchers = new FreeList("iowatcher", 100, function () {
|
||||
return new IOWatcher();
|
||||
});
|
||||
|
||||
|
||||
var nb = 0;
|
||||
var buffers = new FreeList("buffer", 100, function (l) {
|
||||
return new Buffer(l);
|
||||
});
|
||||
|
||||
|
||||
// Allocated on demand.
|
||||
var recvBuffer = null;
|
||||
function allocRecvBuffer () {
|
||||
recvBuffer = new Buffer(40*1024);
|
||||
recvBuffer.used = 0;
|
||||
}
|
||||
|
||||
|
||||
function _doFlush () {
|
||||
var socket = this.socket;
|
||||
// Stream becomes writeable on connect() but don't flush if there's
|
||||
// nothing actually to write
|
||||
if (socket._writeQueueSize == 0) {
|
||||
return;
|
||||
}
|
||||
if (socket.flush()) {
|
||||
assert(socket._writeQueueSize == 0);
|
||||
if (socket._events && socket._events['drain']) socket.emit("drain");
|
||||
if (socket.ondrain) socket.ondrain(); // Optimization
|
||||
}
|
||||
}
|
||||
|
||||
function initStream (self) {
|
||||
self._readWatcher = ioWatchers.alloc();
|
||||
self._readWatcher.callback = function () {
|
||||
// If this is the first recv (recvBuffer doesn't exist) or we've used up
|
||||
// most of the recvBuffer, allocate a new one.
|
||||
if (recvBuffer) {
|
||||
if (recvBuffer.length - recvBuffer.used < 128) {
|
||||
// discard the old recvBuffer. Can't add to the free list because
|
||||
// users might have refernces to slices on it.
|
||||
recvBuffer = null;
|
||||
allocRecvBuffer();
|
||||
}
|
||||
} else {
|
||||
allocRecvBuffer();
|
||||
}
|
||||
|
||||
//debug('recvBuffer.used ' + recvBuffer.used);
|
||||
var bytesRead;
|
||||
|
||||
try {
|
||||
bytesRead = read(self.fd,
|
||||
recvBuffer,
|
||||
recvBuffer.used,
|
||||
recvBuffer.length - recvBuffer.used);
|
||||
} catch (e) {
|
||||
self.forceClose(e);
|
||||
return;
|
||||
}
|
||||
|
||||
//debug('bytesRead ' + bytesRead + '\n');
|
||||
|
||||
if (bytesRead == 0) {
|
||||
self.readable = false;
|
||||
self._readWatcher.stop();
|
||||
|
||||
if (self._events && self._events['end']) self.emit('end');
|
||||
if (self.onend) self.onend();
|
||||
|
||||
if (!self.writable) self.forceClose();
|
||||
} else if (bytesRead > 0) {
|
||||
|
||||
timeout.active(self);
|
||||
|
||||
var start = recvBuffer.used;
|
||||
var end = recvBuffer.used + bytesRead;
|
||||
|
||||
if (!self._encoding) {
|
||||
if (self._events && self._events['data']) {
|
||||
// emit a slice
|
||||
self.emit('data', recvBuffer.slice(start, end));
|
||||
}
|
||||
|
||||
// Optimization: emit the original buffer with end points
|
||||
if (self.ondata) self.ondata(recvBuffer, start, end);
|
||||
} else {
|
||||
// TODO remove me - we should only output Buffer
|
||||
|
||||
var string;
|
||||
switch (self._encoding) {
|
||||
case 'utf8':
|
||||
string = recvBuffer.utf8Slice(start, end);
|
||||
break;
|
||||
case 'ascii':
|
||||
string = recvBuffer.asciiSlice(start, end);
|
||||
break;
|
||||
case 'binary':
|
||||
string = recvBuffer.binarySlice(start, end);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported encoding ' + self._encoding + '. Use Buffer');
|
||||
}
|
||||
self.emit('data', string);
|
||||
}
|
||||
|
||||
|
||||
recvBuffer.used += bytesRead;
|
||||
}
|
||||
};
|
||||
self.readable = false;
|
||||
|
||||
self._writeQueue = []; // queue of buffers that need to be written to socket
|
||||
// XXX use link list?
|
||||
self._writeQueueSize = 0; // in bytes, not to be confused with _writeQueue.length!
|
||||
|
||||
self._writeWatcher = ioWatchers.alloc();
|
||||
self._writeWatcher.socket = self;
|
||||
self._writeWatcher.callback = _doFlush;
|
||||
self.writable = false;
|
||||
}
|
||||
|
||||
function Stream (fd) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.fd = null;
|
||||
|
||||
if (parseInt(fd) >= 0) {
|
||||
this.open(fd);
|
||||
}
|
||||
};
|
||||
sys.inherits(Stream, events.EventEmitter);
|
||||
exports.Stream = Stream;
|
||||
|
||||
|
||||
Stream.prototype.open = function (fd) {
|
||||
initStream(this);
|
||||
|
||||
this.fd = fd;
|
||||
|
||||
this.readable = true;
|
||||
|
||||
this._writeWatcher.set(this.fd, false, true);
|
||||
this.writable = true;
|
||||
}
|
||||
|
||||
|
||||
exports.createConnection = function (port, host) {
|
||||
var s = new Stream();
|
||||
s.connect(port, host);
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
Object.defineProperty(Stream.prototype, 'readyState', {
|
||||
get: function () {
|
||||
if (this._resolving) {
|
||||
return 'opening';
|
||||
} else if (this.readable && this.writable) {
|
||||
return 'open';
|
||||
} else if (this.readable && !this.writable){
|
||||
return 'readOnly';
|
||||
} else if (!this.readable && this.writable){
|
||||
return 'writeOnly';
|
||||
} else {
|
||||
return 'closed';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Stream.prototype._allocateSendBuffer = function () {
|
||||
var b = buffers.alloc(1024);
|
||||
b.used = 0;
|
||||
b.sent = 0;
|
||||
b.isMsg = false;
|
||||
this._writeQueue.push(b);
|
||||
return b;
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype._writeString = function (data, encoding) {
|
||||
var self = this;
|
||||
if (!self.writable) throw new Error('Stream is not writable');
|
||||
var buffer;
|
||||
|
||||
if (self._writeQueue.length == 0) {
|
||||
buffer = self._allocateSendBuffer();
|
||||
} else {
|
||||
buffer = self.__writeQueueLast();
|
||||
if (buffer.length - buffer.used < 64) {
|
||||
buffer = self._allocateSendBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
encoding = (encoding || 'utf8').toLowerCase(); // default to utf8
|
||||
|
||||
var charsWritten;
|
||||
var bytesWritten;
|
||||
|
||||
|
||||
if (encoding == 'utf8') {
|
||||
charsWritten = buffer.utf8Write(data, buffer.used);
|
||||
bytesWritten = Buffer.byteLength(data.slice(0, charsWritten));
|
||||
} else if (encoding == 'ascii') {
|
||||
// ascii
|
||||
charsWritten = buffer.asciiWrite(data, buffer.used);
|
||||
bytesWritten = charsWritten;
|
||||
} else {
|
||||
// binary
|
||||
charsWritten = buffer.binaryWrite(data, buffer.used);
|
||||
bytesWritten = charsWritten;
|
||||
}
|
||||
|
||||
buffer.used += bytesWritten;
|
||||
self._writeQueueSize += bytesWritten;
|
||||
|
||||
//debug('charsWritten ' + charsWritten);
|
||||
//debug('buffer.used ' + buffer.used);
|
||||
|
||||
// If we didn't finish, then recurse with the rest of the string.
|
||||
if (charsWritten < data.length) {
|
||||
//debug('recursive write');
|
||||
self._writeString(data.slice(charsWritten), encoding);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.__writeQueueLast = function () {
|
||||
return this._writeQueue.length > 0 ? this._writeQueue[this._writeQueue.length-1]
|
||||
: null;
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.send = function () {
|
||||
throw new Error('send renamed to write');
|
||||
};
|
||||
|
||||
Stream.prototype.setEncoding = function (enc) {
|
||||
// TODO check values, error out on bad, and deprecation message?
|
||||
this._encoding = enc.toLowerCase();
|
||||
};
|
||||
|
||||
// Returns true if all the data was flushed to socket. Returns false if
|
||||
// something was queued. If data was queued, then the "drain" event will
|
||||
// signal when it has been finally flushed to socket.
|
||||
Stream.prototype.write = function (data, encoding) {
|
||||
var self = this;
|
||||
|
||||
if (!self.writable) throw new Error('Stream is not writable');
|
||||
|
||||
if (self._writeQueue && self._writeQueue.length) {
|
||||
// slow
|
||||
// There is already a write queue, so let's append to it.
|
||||
return self._queueWrite(data, encoding);
|
||||
|
||||
} else {
|
||||
// The most common case. There is no write queue. Just push the data
|
||||
// directly to the socket.
|
||||
|
||||
var bytesWritten;
|
||||
var buffer = data, off = 0, len = data.length;
|
||||
|
||||
if (typeof data == 'string') {
|
||||
encoding = (encoding || 'utf8').toLowerCase();
|
||||
var bytes = Buffer.byteLength(data, encoding);
|
||||
|
||||
//debug('write string :' + JSON.stringify(data));
|
||||
|
||||
if (!recvBuffer) allocRecvBuffer();
|
||||
|
||||
if (recvBuffer.length - recvBuffer.used < bytes) {
|
||||
// not enough room - go to slow case
|
||||
return self._queueWrite(data, encoding);
|
||||
}
|
||||
|
||||
var charsWritten;
|
||||
if (encoding == 'utf8') {
|
||||
recvBuffer.utf8Write(data, recvBuffer.used);
|
||||
} else if (encoding == 'ascii') {
|
||||
// ascii
|
||||
recvBuffer.asciiWrite(data, recvBuffer.used);
|
||||
} else {
|
||||
// binary
|
||||
recvBuffer.binaryWrite(data, recvBuffer.used);
|
||||
}
|
||||
|
||||
buffer = recvBuffer;
|
||||
off = recvBuffer.used;
|
||||
len = bytes;
|
||||
}
|
||||
|
||||
//debug('write [fd, off, len] =' + JSON.stringify([self.fd, off, len]));
|
||||
|
||||
// Send the buffer.
|
||||
try {
|
||||
bytesWritten = write(self.fd, buffer, off, len);
|
||||
} catch (e) {
|
||||
self.forceClose(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
//debug('wrote ' + bytesWritten);
|
||||
|
||||
// Note: if using the recvBuffer - we don't need to increase
|
||||
// recvBuffer.used because it was all sent. Just reuse that space.
|
||||
|
||||
if (bytesWritten == len) return true;
|
||||
|
||||
//debug('write incomplete ' + bytesWritten + ' < ' + len);
|
||||
|
||||
if (buffer == data) {
|
||||
data.sent = bytesWritten || 0;
|
||||
data.used = data.length;
|
||||
} else {
|
||||
// string
|
||||
recvBuffer.used += bytesWritten;
|
||||
data = recvBuffer.slice(off+bytesWritten, off+len+bytesWritten);
|
||||
data.sent = 0;
|
||||
data.used = data.length;
|
||||
}
|
||||
|
||||
//if (!self._writeQueue) initWriteStream(self);
|
||||
self._writeQueue.push(data);
|
||||
self._writeQueueSize += data.used;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Stream.prototype._queueWrite = function (data, encoding) {
|
||||
//debug('_queueWrite');
|
||||
var self = this;
|
||||
|
||||
if (self.__writeQueueLast() == END_OF_FILE) {
|
||||
throw new Error('socket.close() called already; cannot write.');
|
||||
}
|
||||
|
||||
if (typeof(data) == 'string') {
|
||||
self._writeString(data, encoding);
|
||||
} else {
|
||||
// data is a Buffer
|
||||
// walk through the _writeQueue, find the first empty buffer
|
||||
//var inserted = false;
|
||||
data.sent = 0;
|
||||
data.used = data.length;
|
||||
self._writeQueue.push(data);
|
||||
self._writeQueueSize += data.used;
|
||||
}
|
||||
return this.flush();
|
||||
};
|
||||
|
||||
// Flushes the write buffer out.
|
||||
// Returns true if the entire buffer was flushed.
|
||||
Stream.prototype.flush = function () {
|
||||
var self = this;
|
||||
|
||||
var bytesWritten;
|
||||
while (self._writeQueue.length) {
|
||||
if (!self.writable) throw new Error('Stream is not writable');
|
||||
|
||||
var b = self._writeQueue[0];
|
||||
|
||||
if (b == END_OF_FILE) {
|
||||
self._shutdown();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b.sent == b.used) {
|
||||
// shift!
|
||||
self._writeQueue.shift();
|
||||
buffers.free(b);
|
||||
continue;
|
||||
}
|
||||
|
||||
var fdToSend = null;
|
||||
|
||||
try {
|
||||
bytesWritten = write(self.fd,
|
||||
b,
|
||||
b.sent,
|
||||
b.used - b.sent);
|
||||
} catch (e) {
|
||||
self.forceClose(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
timeout.active(self);
|
||||
|
||||
|
||||
if (bytesWritten === null) {
|
||||
// EAGAIN
|
||||
debug('write EAGAIN');
|
||||
self._writeWatcher.start();
|
||||
assert(self._writeQueueSize > 0);
|
||||
return false;
|
||||
} else {
|
||||
b.sent += bytesWritten;
|
||||
self._writeQueueSize -= bytesWritten;
|
||||
//debug('bytes sent: ' + b.sent);
|
||||
}
|
||||
}
|
||||
if (self._writeWatcher) self._writeWatcher.stop();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
function doConnect (socket, port, host) {
|
||||
try {
|
||||
connect(socket.fd, port, host);
|
||||
} catch (e) {
|
||||
socket.forceClose(e);
|
||||
}
|
||||
|
||||
// Don't start the read watcher until connection is established
|
||||
socket._readWatcher.set(socket.fd, true, false);
|
||||
|
||||
// How to connect on POSIX: Wait for fd to become writable, then call
|
||||
// socketError() if there isn't an error, we're connected. AFAIK this a
|
||||
// platform independent way determining when a non-blocking connection
|
||||
// is established, but I have only seen it documented in the Linux
|
||||
// Manual Page connect(2) under the error code EINPROGRESS.
|
||||
socket._writeWatcher.set(socket.fd, false, true);
|
||||
socket._writeWatcher.start();
|
||||
socket._writeWatcher.callback = function () {
|
||||
var errno = socketError(socket.fd);
|
||||
if (errno == 0) {
|
||||
// connection established
|
||||
socket.resume();
|
||||
socket.readable = true;
|
||||
socket.writable = true;
|
||||
socket._writeWatcher.callback = socket._doFlush;
|
||||
socket.emit('connect');
|
||||
} else if (errno != EINPROGRESS) {
|
||||
socket.forceClose(errnoException(errno, 'connect'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// var stream = new Stream();
|
||||
// stream.connect(80) - TCP connect to port 80 on the localhost
|
||||
// stream.connect(80, 'nodejs.org') - TCP connect to port 80 on nodejs.org
|
||||
// stream.connect('/tmp/socket') - UNIX connect to socket specified by path
|
||||
Stream.prototype.connect = function () {
|
||||
var self = this;
|
||||
initStream(self);
|
||||
if (self.fd) throw new Error('Stream already opened');
|
||||
if (!self._readWatcher) throw new Error('No readWatcher');
|
||||
|
||||
timeout.active(socket);
|
||||
|
||||
var port = parseInt(arguments[0]);
|
||||
|
||||
if (port >= 0) {
|
||||
self.fd = socket('tcp');
|
||||
//debug('new fd = ' + self.fd);
|
||||
self.type = 'tcp';
|
||||
// TODO dns resolution on arguments[1]
|
||||
var port = arguments[0];
|
||||
self._resolving = true;
|
||||
lookupDomainName(arguments[1], function (ip) {
|
||||
self._resolving = false;
|
||||
doConnect(self, port, ip);
|
||||
});
|
||||
} else {
|
||||
self.fd = socket('unix');
|
||||
self.type = 'unix';
|
||||
// TODO check if sockfile exists?
|
||||
doConnect(self, arguments[0]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.address = function () {
|
||||
return getsockname(this.fd);
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.setNoDelay = function (v) {
|
||||
if (this.type == 'tcp') setNoDelay(this.fd, v);
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.setTimeout = function (msecs) {
|
||||
timeout.enroll(this, msecs);
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.pause = function () {
|
||||
this._readWatcher.stop();
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.resume = function () {
|
||||
if (this.fd === null) throw new Error('Cannot resume() closed Stream.');
|
||||
this._readWatcher.set(this.fd, true, false);
|
||||
this._readWatcher.start();
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.forceClose = function (exception) {
|
||||
// recvBuffer is shared between sockets, so don't need to free it here.
|
||||
var self = this;
|
||||
|
||||
var b;
|
||||
while (this._writeQueue.length) {
|
||||
b = this._writeQueue.shift();
|
||||
if (b instanceof Buffer) buffers.free(b);
|
||||
}
|
||||
|
||||
if (this._writeWatcher) {
|
||||
this._writeWatcher.stop();
|
||||
ioWatchers.free(this._writeWatcher);
|
||||
this._writeWatcher = null;
|
||||
}
|
||||
|
||||
if (this._readWatcher) {
|
||||
this._readWatcher.stop();
|
||||
ioWatchers.free(this._readWatcher);
|
||||
this._readWatcher = null;
|
||||
}
|
||||
|
||||
timeout.unenroll(this);
|
||||
|
||||
// FIXME Bug when this.fd == 0
|
||||
if (this.fd) {
|
||||
close(this.fd);
|
||||
//debug('close ' + this.fd);
|
||||
this.fd = null;
|
||||
process.nextTick(function () {
|
||||
if (exception) self.emit('error', exception);
|
||||
self.emit('close', exception ? true : false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype._shutdown = function () {
|
||||
if (this.writable) {
|
||||
this.writable = false;
|
||||
|
||||
try {
|
||||
shutdown(this.fd, 'write')
|
||||
} catch (e) {
|
||||
this.forceClose(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.readable) this.forceClose();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Stream.prototype.close = function () {
|
||||
if (this.writable) {
|
||||
if (this.__writeQueueLast() != END_OF_FILE) {
|
||||
this._writeQueue.push(END_OF_FILE);
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function Server (listener) {
|
||||
events.EventEmitter.call(this);
|
||||
var self = this;
|
||||
|
||||
if (listener) {
|
||||
self.addListener('connection', listener);
|
||||
}
|
||||
|
||||
self.watcher = new IOWatcher();
|
||||
self.watcher.host = self;
|
||||
self.watcher.callback = function () {
|
||||
while (self.fd) {
|
||||
var peerInfo = accept(self.fd);
|
||||
if (!peerInfo) return;
|
||||
|
||||
var s = new Stream(peerInfo.fd);
|
||||
s.remoteAddress = peerInfo.remoteAddress;
|
||||
s.remotePort = peerInfo.remotePort;
|
||||
s.type = self.type;
|
||||
s.server = self;
|
||||
s.resume();
|
||||
|
||||
self.emit('connection', s);
|
||||
// The 'connect' event probably should be removed for server-side
|
||||
// sockets. It's redundent.
|
||||
s.emit('connect');
|
||||
}
|
||||
};
|
||||
}
|
||||
sys.inherits(Server, events.EventEmitter);
|
||||
exports.Server = Server;
|
||||
|
||||
|
||||
exports.createServer = function (listener) {
|
||||
return new Server(listener);
|
||||
};
|
||||
|
||||
/* This function does both an ipv4 and ipv6 look up.
|
||||
* It first tries the ipv4 look up, if that fails, then it does the ipv6.
|
||||
*/
|
||||
function lookupDomainName (dn, callback) {
|
||||
if (!needsLookup(dn)) {
|
||||
// Always wait until the next tick this is so people can do
|
||||
//
|
||||
// server.listen(8000);
|
||||
// server.addListener('listening', fn);
|
||||
//
|
||||
// Marginally slower, but a lot fewer WTFs.
|
||||
process.nextTick(function () { callback(dn); })
|
||||
} else {
|
||||
debug("getaddrinfo 4 " + dn);
|
||||
getaddrinfo(dn, 4, function (r4) {
|
||||
if (r4 instanceof Error) throw r4;
|
||||
if (r4.length > 0) {
|
||||
debug("getaddrinfo 4 found " + r4);
|
||||
callback(r4[0]);
|
||||
} else {
|
||||
debug("getaddrinfo 6 " + dn);
|
||||
getaddrinfo(dn, 6, function (r6) {
|
||||
if (r6 instanceof Error) throw r6;
|
||||
if (r6.length < 0) {
|
||||
throw new Error("No address associated with hostname " + dn);
|
||||
}
|
||||
debug("getaddrinfo 6 found " + r6);
|
||||
callback(r6[0]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Listen on a UNIX socket
|
||||
// server.listen("/tmp/socket");
|
||||
//
|
||||
// Listen on port 8000, accept connections from INADDR_ANY.
|
||||
// server.listen(8000);
|
||||
//
|
||||
// Listen on port 8000, accept connections to "192.168.1.2"
|
||||
// server.listen(8000, "192.168.1.2");
|
||||
Server.prototype.listen = function () {
|
||||
var self = this;
|
||||
if (self.fd) throw new Error('Server already opened');
|
||||
|
||||
function doListen () {
|
||||
listen(self.fd, 128);
|
||||
self.watcher.set(self.fd, true, false);
|
||||
self.watcher.start();
|
||||
self.emit("listening");
|
||||
}
|
||||
|
||||
if (typeof(arguments[0]) == 'string') {
|
||||
// the first argument specifies a path
|
||||
self.fd = socket('unix');
|
||||
self.type = 'unix';
|
||||
var path = arguments[0];
|
||||
self.path = path;
|
||||
// unlink sockfile if it exists
|
||||
fs.stat(path, function (err, r) {
|
||||
if (err) {
|
||||
if (err.errno == ENOENT) {
|
||||
bind(self.fd, path);
|
||||
doListen();
|
||||
} else {
|
||||
throw r;
|
||||
}
|
||||
} else {
|
||||
if (!r.isFile()) {
|
||||
throw new Error("Non-file exists at " + path);
|
||||
} else {
|
||||
fs.unlink(path, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
bind(self.fd, path);
|
||||
doListen();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (!arguments[0]) {
|
||||
// Don't bind(). OS will assign a port with INADDR_ANY.
|
||||
// The port can be found with server.address()
|
||||
self.fd = socket('tcp');
|
||||
self.type = 'tcp';
|
||||
doListen();
|
||||
} else {
|
||||
// the first argument is the port, the second an IP
|
||||
self.fd = socket('tcp');
|
||||
self.type = 'tcp';
|
||||
var port = arguments[0];
|
||||
lookupDomainName(arguments[1], function (ip) {
|
||||
bind(self.fd, port, ip);
|
||||
doListen();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Server.prototype.address = function () {
|
||||
return getsockname(this.fd);
|
||||
};
|
||||
|
||||
|
||||
Server.prototype.close = function () {
|
||||
var self = this;
|
||||
if (!self.fd) throw new Error('Not running');
|
||||
|
||||
self.watcher.stop();
|
||||
|
||||
close(self.fd);
|
||||
self.fd = null;
|
||||
|
||||
if (self.type === "unix") {
|
||||
fs.unlink(self.path, function () {
|
||||
self.emit("close");
|
||||
});
|
||||
} else {
|
||||
self.emit("close");
|
||||
}
|
||||
};
|
10
lib/repl.js
10
lib/repl.js
@ -12,13 +12,17 @@ exports.scope = {};
|
||||
exports.prompt = "node> ";
|
||||
// Can overridden with custom print functions, such as `probe` or `eyes.js`
|
||||
exports.writer = sys.p;
|
||||
|
||||
var stdin;
|
||||
|
||||
exports.start = function (prompt) {
|
||||
if (prompt !== undefined) {
|
||||
exports.prompt = prompt;
|
||||
}
|
||||
|
||||
process.stdio.open();
|
||||
process.stdio.addListener("data", readline);
|
||||
stdin = process.openStdin();
|
||||
stdin.setEncoding('utf8');
|
||||
stdin.addListener("data", readline);
|
||||
displayPrompt();
|
||||
}
|
||||
|
||||
@ -96,7 +100,7 @@ function parseREPLKeyword (cmd) {
|
||||
displayPrompt();
|
||||
return true;
|
||||
case ".exit":
|
||||
process.stdio.close();
|
||||
stdin.close();
|
||||
return true;
|
||||
case ".help":
|
||||
sys.puts(".break\tSometimes you get stuck in a place you can't get out... This will get you out.");
|
||||
|
43
lib/sys.js
43
lib/sys.js
@ -2,23 +2,23 @@ var events = require('events');
|
||||
|
||||
exports.print = function () {
|
||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||
process.stdio.write(arguments[i]);
|
||||
process.stdout.write(arguments[i]);
|
||||
}
|
||||
};
|
||||
|
||||
exports.puts = function () {
|
||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||
process.stdio.write(arguments[i] + '\n');
|
||||
process.stdout.write(arguments[i] + '\n');
|
||||
}
|
||||
};
|
||||
|
||||
exports.debug = function (x) {
|
||||
process.stdio.writeError("DEBUG: " + x + "\n");
|
||||
process.binding('stdio').writeError("DEBUG: " + x + "\n");
|
||||
};
|
||||
|
||||
exports.error = function (x) {
|
||||
var error = exports.error = function (x) {
|
||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||
process.stdio.writeError(arguments[i] + '\n');
|
||||
process.binding('stdio').writeError(arguments[i] + '\n');
|
||||
}
|
||||
};
|
||||
|
||||
@ -184,7 +184,7 @@ exports.inspect = function (obj, showHidden, depth) {
|
||||
|
||||
exports.p = function () {
|
||||
for (var i = 0, len = arguments.length; i < len; ++i) {
|
||||
exports.error(exports.inspect(arguments[i]));
|
||||
error(exports.inspect(arguments[i]));
|
||||
}
|
||||
};
|
||||
|
||||
@ -207,29 +207,14 @@ exports.log = function (msg) {
|
||||
exports.puts(timestamp() + ' - ' + msg.toString());
|
||||
}
|
||||
|
||||
exports.exec = function (command, callback) {
|
||||
var child = process.createChildProcess("/bin/sh", ["-c", command]);
|
||||
var stdout = "";
|
||||
var stderr = "";
|
||||
|
||||
child.addListener("output", function (chunk) {
|
||||
if (chunk) stdout += chunk;
|
||||
});
|
||||
|
||||
child.addListener("error", function (chunk) {
|
||||
if (chunk) stderr += chunk;
|
||||
});
|
||||
|
||||
child.addListener("exit", function (code) {
|
||||
if (code == 0) {
|
||||
if (callback) callback(null, stdout, stderr);
|
||||
} else {
|
||||
var e = new Error("Command failed: " + stderr);
|
||||
e.code = code;
|
||||
if (callback) callback(e, stdout, stderr);
|
||||
}
|
||||
});
|
||||
};
|
||||
var execWarning;
|
||||
exports.exec = function () {
|
||||
if (!execWarning) {
|
||||
execWarning = 'sys.exec has moved to the "child_process" module. Please update your source code.'
|
||||
error(execWarning);
|
||||
}
|
||||
return require('child_process').exec.apply(this, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another.
|
||||
|
31
lib/tcp.js
31
lib/tcp.js
@ -1,26 +1,11 @@
|
||||
var tcp = process.binding('tcp');
|
||||
var net = require('net');
|
||||
var sys = require('sys');
|
||||
|
||||
var TLS_STATUS_CODES = {
|
||||
1 : 'JS_GNUTLS_CERT_VALIDATED',
|
||||
0 : 'JS_GNUTLS_CERT_UNDEFINED',
|
||||
var warning;
|
||||
if (!warning) {
|
||||
warning = "The 'tcp' module is now called 'net'. Otherwise it should have a similar interface.";
|
||||
sys.error(warning);
|
||||
}
|
||||
TLS_STATUS_CODES[-100] = 'JS_GNUTLS_CERT_SIGNER_NOT_FOUND';
|
||||
TLS_STATUS_CODES[-101] = 'JS_GNUTLS_CERT_SIGNER_NOT_CA';
|
||||
TLS_STATUS_CODES[-102] = 'JS_GNUTLS_CERT_INVALID';
|
||||
TLS_STATUS_CODES[-103] = 'JS_GNUTLS_CERT_NOT_ACTIVATED';
|
||||
TLS_STATUS_CODES[-104] = 'JS_GNUTLS_CERT_EXPIRED';
|
||||
TLS_STATUS_CODES[-105] = 'JS_GNUTLS_CERT_REVOKED';
|
||||
TLS_STATUS_CODES[-106] = 'JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME';
|
||||
|
||||
exports.createServer = function (on_connection, options) {
|
||||
var server = new tcp.Server();
|
||||
server.addListener("connection", on_connection);
|
||||
//server.setOptions(options);
|
||||
return server;
|
||||
};
|
||||
|
||||
exports.createConnection = function (port, host) {
|
||||
var connection = new tcp.Connection();
|
||||
connection.connect(port, host);
|
||||
return connection;
|
||||
};
|
||||
exports.createServer = net.createServer;
|
||||
exports.createConnection = net.createConnection;
|
||||
|
26
lib/tcp_old.js
Normal file
26
lib/tcp_old.js
Normal file
@ -0,0 +1,26 @@
|
||||
var tcp = process.binding('tcp');
|
||||
|
||||
var TLS_STATUS_CODES = {
|
||||
1 : 'JS_GNUTLS_CERT_VALIDATED',
|
||||
0 : 'JS_GNUTLS_CERT_UNDEFINED',
|
||||
}
|
||||
TLS_STATUS_CODES[-100] = 'JS_GNUTLS_CERT_SIGNER_NOT_FOUND';
|
||||
TLS_STATUS_CODES[-101] = 'JS_GNUTLS_CERT_SIGNER_NOT_CA';
|
||||
TLS_STATUS_CODES[-102] = 'JS_GNUTLS_CERT_INVALID';
|
||||
TLS_STATUS_CODES[-103] = 'JS_GNUTLS_CERT_NOT_ACTIVATED';
|
||||
TLS_STATUS_CODES[-104] = 'JS_GNUTLS_CERT_EXPIRED';
|
||||
TLS_STATUS_CODES[-105] = 'JS_GNUTLS_CERT_REVOKED';
|
||||
TLS_STATUS_CODES[-106] = 'JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME';
|
||||
|
||||
exports.createServer = function (on_connection, options) {
|
||||
var server = new tcp.Server();
|
||||
server.addListener("connection", on_connection);
|
||||
//server.setOptions(options);
|
||||
return server;
|
||||
};
|
||||
|
||||
exports.createConnection = function (port, host) {
|
||||
var connection = new tcp.Connection();
|
||||
connection.connect(port, host);
|
||||
return connection;
|
||||
};
|
72
src/node.cc
72
src/node.cc
@ -1,6 +1,8 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#include <node.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -12,12 +14,16 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h> /* setuid, getuid */
|
||||
|
||||
#include <node_buffer.h>
|
||||
#include <node_io_watcher.h>
|
||||
#include <node_net2.h>
|
||||
#include <node_events.h>
|
||||
#include <node_dns.h>
|
||||
#include <node_net.h>
|
||||
#include <node_file.h>
|
||||
#include <node_idle_watcher.h>
|
||||
#include <node_http.h>
|
||||
#include <node_http_parser.h>
|
||||
#include <node_signal_watcher.h>
|
||||
#include <node_stat_watcher.h>
|
||||
#include <node_timer.h>
|
||||
@ -533,6 +539,13 @@ static Handle<Value> SetUid(const Arguments& args) {
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
Handle<Value>
|
||||
NowGetter (Local<String> property, const AccessorInfo& info)
|
||||
{
|
||||
HandleScope scope;
|
||||
return scope.Close(Integer::New(ev_now(EV_DEFAULT_UC)));
|
||||
}
|
||||
|
||||
|
||||
v8::Handle<v8::Value> Exit(const v8::Arguments& args) {
|
||||
HandleScope scope;
|
||||
@ -1070,7 +1083,18 @@ static Handle<Value> Binding(const Arguments& args) {
|
||||
|
||||
Local<Object> exports;
|
||||
|
||||
if (!strcmp(*module_v, "http")) {
|
||||
// TODO DRY THIS UP!
|
||||
|
||||
if (!strcmp(*module_v, "stdio")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
exports = Object::New();
|
||||
Stdio::Initialize(exports);
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "http")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
@ -1127,6 +1151,42 @@ static Handle<Value> Binding(const Arguments& args) {
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "net")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
exports = Object::New();
|
||||
InitNet2(exports);
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "http_parser")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
exports = Object::New();
|
||||
InitHttpParser(exports);
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "child_process")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
exports = Object::New();
|
||||
ChildProcess::Initialize(exports);
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "buffer")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
} else {
|
||||
exports = Object::New();
|
||||
Buffer::Initialize(exports);
|
||||
binding_cache->Set(module, exports);
|
||||
}
|
||||
|
||||
} else if (!strcmp(*module_v, "natives")) {
|
||||
if (binding_cache->Has(module)) {
|
||||
exports = binding_cache->Get(module)->ToObject();
|
||||
@ -1135,19 +1195,24 @@ static Handle<Value> Binding(const Arguments& args) {
|
||||
// Explicitly define native sources.
|
||||
// TODO DRY/automate this?
|
||||
exports->Set(String::New("assert"), String::New(native_assert));
|
||||
exports->Set(String::New("buffer"), String::New(native_buffer));
|
||||
exports->Set(String::New("child_process"),String::New(native_child_process));
|
||||
exports->Set(String::New("dns"), String::New(native_dns));
|
||||
exports->Set(String::New("events"), String::New(native_events));
|
||||
exports->Set(String::New("file"), String::New(native_file));
|
||||
exports->Set(String::New("fs"), String::New(native_fs));
|
||||
exports->Set(String::New("http"), String::New(native_http));
|
||||
exports->Set(String::New("http_old"), String::New(native_http_old));
|
||||
exports->Set(String::New("ini"), String::New(native_ini));
|
||||
exports->Set(String::New("mjsunit"), String::New(native_mjsunit));
|
||||
exports->Set(String::New("multipart"), String::New(native_multipart));
|
||||
exports->Set(String::New("net"), String::New(native_net));
|
||||
exports->Set(String::New("posix"), String::New(native_posix));
|
||||
exports->Set(String::New("querystring"), String::New(native_querystring));
|
||||
exports->Set(String::New("repl"), String::New(native_repl));
|
||||
exports->Set(String::New("sys"), String::New(native_sys));
|
||||
exports->Set(String::New("tcp"), String::New(native_tcp));
|
||||
exports->Set(String::New("tcp_old"), String::New(native_tcp_old));
|
||||
exports->Set(String::New("uri"), String::New(native_uri));
|
||||
exports->Set(String::New("url"), String::New(native_url));
|
||||
exports->Set(String::New("utils"), String::New(native_utils));
|
||||
@ -1169,6 +1234,8 @@ static void Load(int argc, char *argv[]) {
|
||||
Local<FunctionTemplate> process_template = FunctionTemplate::New();
|
||||
node::EventEmitter::Initialize(process_template);
|
||||
|
||||
process_template->InstanceTemplate()->SetAccessor(String::NewSymbol("now"), NowGetter, NULL);
|
||||
|
||||
process = Persistent<Object>::New(process_template->GetFunction()->NewInstance());
|
||||
|
||||
// Add a reference to the global object
|
||||
@ -1247,10 +1314,9 @@ static void Load(int argc, char *argv[]) {
|
||||
|
||||
|
||||
// Initialize the C++ modules..................filename of module
|
||||
IOWatcher::Initialize(process); // io_watcher.cc
|
||||
IdleWatcher::Initialize(process); // idle_watcher.cc
|
||||
Stdio::Initialize(process); // stdio.cc
|
||||
Timer::Initialize(process); // timer.cc
|
||||
ChildProcess::Initialize(process); // child_process.cc
|
||||
DefineConstants(process); // constants.cc
|
||||
|
||||
// Compile, execute the src/node.js file. (Which was included as static C
|
||||
|
19
src/node.h
19
src/node.h
@ -58,5 +58,24 @@ ssize_t DecodeWrite(char *buf,
|
||||
v8::Local<v8::Object> BuildStatsObject(struct stat * s);
|
||||
|
||||
|
||||
static inline v8::Persistent<v8::Function>* cb_persist(
|
||||
const v8::Local<v8::Value> &v) {
|
||||
v8::Persistent<v8::Function> *fn = new v8::Persistent<v8::Function>();
|
||||
*fn = v8::Persistent<v8::Function>::New(v8::Local<v8::Function>::Cast(v));
|
||||
return fn;
|
||||
}
|
||||
|
||||
static inline v8::Persistent<v8::Function>* cb_unwrap(void *data) {
|
||||
v8::Persistent<v8::Function> *cb =
|
||||
reinterpret_cast<v8::Persistent<v8::Function>*>(data);
|
||||
assert((*cb)->IsFunction());
|
||||
return cb;
|
||||
}
|
||||
|
||||
static inline void cb_destroy(v8::Persistent<v8::Function> * cb) {
|
||||
cb->Dispose();
|
||||
delete cb;
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
#endif // SRC_NODE_H_
|
||||
|
45
src/node.js
45
src/node.js
@ -25,6 +25,7 @@ process.unwatchFile = removed("process.unwatchFile() has moved to fs.unwatchFile
|
||||
GLOBAL.node = {};
|
||||
|
||||
node.createProcess = removed("node.createProcess() has been changed to process.createChildProcess() update your code");
|
||||
process.createChildProcess = removed("childProcess API has changed. See doc/api.txt.");
|
||||
node.exec = removed("process.exec() has moved. Use require('sys') to bring it back.");
|
||||
node.inherits = removed("node.inherits() has moved. Use require('sys') to access it.");
|
||||
process.inherits = removed("process.inherits() has moved to sys.inherits.");
|
||||
@ -94,27 +95,11 @@ function requireNative (id) {
|
||||
}
|
||||
|
||||
|
||||
process.createChildProcess = function (file, args, env) {
|
||||
var child = new process.ChildProcess();
|
||||
args = args || [];
|
||||
env = env || process.env;
|
||||
var envPairs = [];
|
||||
for (var key in env) {
|
||||
if (env.hasOwnProperty(key)) {
|
||||
envPairs.push(key + "=" + env[key]);
|
||||
}
|
||||
}
|
||||
// TODO Note envPairs is not currently used in child_process.cc. The PATH
|
||||
// needs to be searched for the 'file' command if 'file' does not contain
|
||||
// a '/' character.
|
||||
child.spawn(file, args, envPairs);
|
||||
return child;
|
||||
};
|
||||
|
||||
process.assert = function (x, msg) {
|
||||
if (!(x)) throw new Error(msg || "assertion error");
|
||||
};
|
||||
|
||||
|
||||
// From jQuery.extend in the jQuery JavaScript Library v1.3.2
|
||||
// Copyright (c) 2009 John Resig
|
||||
// Dual licensed under the MIT and GPL licenses.
|
||||
@ -124,7 +109,7 @@ var mixinMessage;
|
||||
process.mixin = function() {
|
||||
if (!mixinMessage) {
|
||||
mixinMessage = 'deprecation warning: process.mixin will be removed from node-core future releases.\n'
|
||||
process.stdio.writeError(mixinMessage);
|
||||
process.binding('stdio').writeError(mixinMessage);
|
||||
}
|
||||
// copy reference to target object
|
||||
var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source;
|
||||
@ -162,7 +147,7 @@ process.mixin = function() {
|
||||
else {
|
||||
// Prevent never-ending loop
|
||||
if (target === d.value) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (deep && d.value && typeof d.value === "object") {
|
||||
@ -343,7 +328,7 @@ if ("NODE_DEBUG" in process.env) debugLevel = 1;
|
||||
|
||||
function debug (x) {
|
||||
if (debugLevel > 0) {
|
||||
process.stdio.writeError(x + "\n");
|
||||
process.binding('stdio').writeError(x + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,6 +765,26 @@ Module.prototype._waitChildrenLoad = function (callback) {
|
||||
};
|
||||
|
||||
|
||||
var stdout;
|
||||
process.__defineGetter__('stdout', function () {
|
||||
if (stdout) return stdout;
|
||||
var net = requireNative('net');
|
||||
stdout = new net.Stream(process.binding('stdio').stdoutFD);
|
||||
return stdout;
|
||||
});
|
||||
|
||||
var stdin;
|
||||
process.openStdin = function () {
|
||||
if (stdin) return stdin;
|
||||
var net = requireNative('net');
|
||||
var fd = process.binding('stdio').openStdin();
|
||||
stdin = new net.Stream(fd);
|
||||
stdin.resume();
|
||||
stdin.readable = true;
|
||||
return stdin;
|
||||
};
|
||||
|
||||
|
||||
process.exit = function (code) {
|
||||
process.emit("exit");
|
||||
process.reallyExit(code);
|
||||
|
426
src/node_buffer.cc
Normal file
426
src/node_buffer.cc
Normal file
@ -0,0 +1,426 @@
|
||||
#include <node_buffer.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h> // malloc, free
|
||||
#include <v8.h>
|
||||
|
||||
#include <arpa/inet.h> // htons, htonl
|
||||
|
||||
#include <node.h>
|
||||
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
namespace node {
|
||||
|
||||
using namespace v8;
|
||||
|
||||
#define SLICE_ARGS(start_arg, end_arg) \
|
||||
if (!start_arg->IsInt32() || !end_arg->IsInt32()) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
} \
|
||||
int32_t start = start_arg->Int32Value(); \
|
||||
int32_t end = end_arg->Int32Value(); \
|
||||
if (start < 0 || end < 0) { \
|
||||
return ThrowException(Exception::TypeError( \
|
||||
String::New("Bad argument."))); \
|
||||
} \
|
||||
if (!(start <= end)) { \
|
||||
return ThrowException(Exception::Error( \
|
||||
String::New("Must have start <= end"))); \
|
||||
} \
|
||||
if ((size_t)end > parent->length_) { \
|
||||
return ThrowException(Exception::Error( \
|
||||
String::New("end cannot be longer than parent.length"))); \
|
||||
}
|
||||
|
||||
|
||||
static Persistent<String> length_symbol;
|
||||
Persistent<FunctionTemplate> Buffer::constructor_template;
|
||||
|
||||
|
||||
// Each javascript Buffer object is backed by a Blob object.
|
||||
// the Blob is just a C-level chunk of bytes.
|
||||
// It has a reference count.
|
||||
struct Blob_ {
|
||||
unsigned int refs;
|
||||
size_t length;
|
||||
char data[1];
|
||||
};
|
||||
typedef struct Blob_ Blob;
|
||||
|
||||
|
||||
static inline Blob * blob_new(size_t length) {
|
||||
size_t s = sizeof(Blob) - 1 + length;
|
||||
Blob * blob = (Blob*) malloc(s);
|
||||
if (!blob) return NULL;
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(s);
|
||||
blob->length = length;
|
||||
blob->refs = 0;
|
||||
//fprintf(stderr, "alloc %d bytes\n", length);
|
||||
return blob;
|
||||
}
|
||||
|
||||
|
||||
static inline void blob_ref(Blob *blob) {
|
||||
blob->refs++;
|
||||
}
|
||||
|
||||
|
||||
static inline void blob_unref(Blob *blob) {
|
||||
assert(blob->refs > 0);
|
||||
if (--blob->refs == 0) {
|
||||
//fprintf(stderr, "free %d bytes\n", blob->length);
|
||||
size_t s = sizeof(Blob) - 1 + blob->length;
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(-s);
|
||||
free(blob);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// When someone calls buffer.asciiSlice, data is not copied. Instead V8
|
||||
// references in the underlying Blob with this ExternalAsciiStringResource.
|
||||
class AsciiSliceExt: public String::ExternalAsciiStringResource {
|
||||
friend class Buffer;
|
||||
public:
|
||||
AsciiSliceExt(Buffer *parent, size_t start, size_t end) {
|
||||
blob_ = parent->blob();
|
||||
blob_ref(blob_);
|
||||
|
||||
assert(start <= end);
|
||||
length_ = end - start;
|
||||
assert(start + length_ <= parent->length());
|
||||
data_ = parent->data() + start;
|
||||
}
|
||||
|
||||
|
||||
~AsciiSliceExt() {
|
||||
//fprintf(stderr, "free ascii slice (%d refs left)\n", blob_->refs);
|
||||
blob_unref(blob_);
|
||||
}
|
||||
|
||||
|
||||
const char* data() const { return data_; }
|
||||
size_t length() const { return length_; }
|
||||
|
||||
private:
|
||||
const char *data_;
|
||||
size_t length_;
|
||||
Blob *blob_;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
Handle<Value> Buffer::New(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *buffer;
|
||||
if (args[0]->IsInt32()) {
|
||||
// var buffer = new Buffer(1024);
|
||||
size_t length = args[0]->Uint32Value();
|
||||
buffer = new Buffer(length);
|
||||
|
||||
} else if (Buffer::HasInstance(args[0]) && args.Length() > 2) {
|
||||
// var slice = new Buffer(buffer, 123, 130);
|
||||
// args: parent, start, end
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());
|
||||
SLICE_ARGS(args[1], args[2])
|
||||
buffer = new Buffer(parent, start, end);
|
||||
} else {
|
||||
return ThrowException(Exception::TypeError(String::New("Bad argument")));
|
||||
}
|
||||
|
||||
buffer->Wrap(args.This());
|
||||
args.This()->SetIndexedPropertiesToExternalArrayData((void*)buffer->data_,
|
||||
kExternalUnsignedByteArray,
|
||||
buffer->length_);
|
||||
args.This()->Set(length_symbol, Integer::New(buffer->length_));
|
||||
return args.This();
|
||||
}
|
||||
|
||||
|
||||
Buffer::Buffer(size_t length) : ObjectWrap() {
|
||||
blob_ = blob_new(length);
|
||||
length_ = length;
|
||||
data_ = blob_->data;
|
||||
blob_ref(blob_);
|
||||
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer));
|
||||
}
|
||||
|
||||
|
||||
Buffer::Buffer(Buffer *parent, size_t start, size_t end) : ObjectWrap() {
|
||||
blob_ = parent->blob_;
|
||||
assert(blob_->refs > 0);
|
||||
blob_ref(blob_);
|
||||
|
||||
assert(start <= end);
|
||||
length_ = end - start;
|
||||
assert(length_ <= parent->length_);
|
||||
data_ = parent->data_ + start;
|
||||
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(sizeof(Buffer));
|
||||
}
|
||||
|
||||
|
||||
Buffer::~Buffer() {
|
||||
assert(blob_->refs > 0);
|
||||
//fprintf(stderr, "free buffer (%d refs left)\n", blob_->refs);
|
||||
blob_unref(blob_);
|
||||
V8::AdjustAmountOfExternalAllocatedMemory(-static_cast<long int>(sizeof(Buffer)));
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::BinarySlice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
SLICE_ARGS(args[0], args[1])
|
||||
|
||||
const char *data = const_cast<char*>(parent->data_ + start);
|
||||
//Local<String> string = String::New(data, end - start);
|
||||
|
||||
Local<Value> b = Encode(data, end - start, BINARY);
|
||||
|
||||
return scope.Close(b);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::AsciiSlice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
SLICE_ARGS(args[0], args[1])
|
||||
|
||||
#if 0
|
||||
AsciiSliceExt *ext = new AsciiSliceExt(parent, start, end);
|
||||
Local<String> string = String::NewExternal(ext);
|
||||
// There should be at least two references to the blob now - the parent
|
||||
// and the slice.
|
||||
assert(parent->blob_->refs >= 2);
|
||||
#endif
|
||||
|
||||
const char *data = const_cast<char*>(parent->data_ + start);
|
||||
Local<String> string = String::New(data, end - start);
|
||||
|
||||
|
||||
return scope.Close(string);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::Utf8Slice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Buffer *parent = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
SLICE_ARGS(args[0], args[1])
|
||||
const char *data = const_cast<char*>(parent->data_ + start);
|
||||
Local<String> string = String::New(data, end - start);
|
||||
return scope.Close(string);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::Slice(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Local<Value> argv[3] = { args.This(), args[0], args[1] };
|
||||
Local<Object> slice =
|
||||
constructor_template->GetFunction()->NewInstance(3, argv);
|
||||
return scope.Close(slice);
|
||||
}
|
||||
|
||||
|
||||
// var charsWritten = buffer.utf8Write(string, offset);
|
||||
Handle<Value> Buffer::Utf8Write(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Argument must be a string")));
|
||||
}
|
||||
|
||||
Local<String> s = args[0]->ToString();
|
||||
|
||||
size_t offset = args[1]->Int32Value();
|
||||
|
||||
if (offset >= buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Offset is out of bounds")));
|
||||
}
|
||||
|
||||
const char *p = buffer->data_ + offset;
|
||||
|
||||
int written = s->WriteUtf8((char*)p, buffer->length_ - offset);
|
||||
|
||||
return scope.Close(Integer::New(written));
|
||||
}
|
||||
|
||||
|
||||
// var charsWritten = buffer.asciiWrite(string, offset);
|
||||
Handle<Value> Buffer::AsciiWrite(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Argument must be a string")));
|
||||
}
|
||||
|
||||
Local<String> s = args[0]->ToString();
|
||||
|
||||
size_t offset = args[1]->Int32Value();
|
||||
|
||||
if (offset >= buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Offset is out of bounds")));
|
||||
}
|
||||
|
||||
const char *p = buffer->data_ + offset;
|
||||
|
||||
size_t towrite = MIN((unsigned long) s->Length(), buffer->length_ - offset);
|
||||
|
||||
int written = s->WriteAscii((char*)p, 0, towrite);
|
||||
return scope.Close(Integer::New(written));
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Buffer::BinaryWrite(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Argument must be a string")));
|
||||
}
|
||||
|
||||
Local<String> s = args[0]->ToString();
|
||||
|
||||
size_t offset = args[1]->Int32Value();
|
||||
|
||||
if (offset >= buffer->length_) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Offset is out of bounds")));
|
||||
}
|
||||
|
||||
char *p = (char*)buffer->data_ + offset;
|
||||
|
||||
size_t towrite = MIN((unsigned long) s->Length(), buffer->length_ - offset);
|
||||
|
||||
int written = DecodeWrite(p, towrite, s, BINARY);
|
||||
return scope.Close(Integer::New(written));
|
||||
}
|
||||
|
||||
|
||||
// buffer.unpack(format, index);
|
||||
// Starting at 'index', unpacks binary from the buffer into an array.
|
||||
// 'format' is a string
|
||||
//
|
||||
// FORMAT RETURNS
|
||||
// N uint32_t a 32bit unsigned integer in network byte order
|
||||
// n uint16_t a 16bit unsigned integer in network byte order
|
||||
// o uint8_t a 8bit unsigned integer
|
||||
Handle<Value> Buffer::Unpack(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(args.This());
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Argument must be a string")));
|
||||
}
|
||||
|
||||
String::AsciiValue format(args[0]->ToString());
|
||||
uint32_t index = args[1]->Uint32Value();
|
||||
|
||||
#define OUT_OF_BOUNDS ThrowException(Exception::Error(String::New("Out of bounds")))
|
||||
|
||||
Local<Array> array = Array::New(format.length());
|
||||
|
||||
uint8_t uint8;
|
||||
uint16_t uint16;
|
||||
uint32_t uint32;
|
||||
|
||||
for (int i = 0; i < format.length(); i++) {
|
||||
switch ((*format)[i]) {
|
||||
// 32bit unsigned integer in network byte order
|
||||
case 'N':
|
||||
if (index + 3 >= buffer->length_) return OUT_OF_BOUNDS;
|
||||
uint32 = htonl(*(uint32_t*)(buffer->data_ + index));
|
||||
array->Set(Integer::New(i), Integer::NewFromUnsigned(uint32));
|
||||
index += 4;
|
||||
break;
|
||||
|
||||
// 16bit unsigned integer in network byte order
|
||||
case 'n':
|
||||
if (index + 1 >= buffer->length_) return OUT_OF_BOUNDS;
|
||||
uint16 = htons(*(uint16_t*)(buffer->data_ + index));
|
||||
array->Set(Integer::New(i), Integer::NewFromUnsigned(uint16));
|
||||
index += 2;
|
||||
break;
|
||||
|
||||
// a single octet, unsigned.
|
||||
case 'o':
|
||||
if (index >= buffer->length_) return OUT_OF_BOUNDS;
|
||||
uint8 = (uint8_t)buffer->data_[index];
|
||||
array->Set(Integer::New(i), Integer::NewFromUnsigned(uint8));
|
||||
index += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Unknown format character")));
|
||||
}
|
||||
}
|
||||
|
||||
return scope.Close(array);
|
||||
}
|
||||
|
||||
|
||||
// var nbytes = Buffer.byteLength("string", "utf8")
|
||||
Handle<Value> Buffer::ByteLength(const Arguments &args) {
|
||||
HandleScope scope;
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
return ThrowException(Exception::TypeError(String::New(
|
||||
"Argument must be a string")));
|
||||
}
|
||||
|
||||
Local<String> s = args[0]->ToString();
|
||||
enum encoding e = ParseEncoding(args[1], UTF8);
|
||||
|
||||
Local<Integer> length =
|
||||
Integer::New(e == UTF8 ? s->Utf8Length() : s->Length());
|
||||
|
||||
return scope.Close(length);
|
||||
}
|
||||
|
||||
|
||||
void Buffer::Initialize(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
length_symbol = Persistent<String>::New(String::NewSymbol("length"));
|
||||
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(Buffer::New);
|
||||
constructor_template = Persistent<FunctionTemplate>::New(t);
|
||||
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
constructor_template->SetClassName(String::NewSymbol("Buffer"));
|
||||
|
||||
// copy free
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "binarySlice", Buffer::BinarySlice);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiSlice", Buffer::AsciiSlice);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "slice", Buffer::Slice);
|
||||
// TODO NODE_SET_PROTOTYPE_METHOD(t, "utf16Slice", Utf16Slice);
|
||||
// copy
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Slice", Buffer::Utf8Slice);
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "utf8Write", Buffer::Utf8Write);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "asciiWrite", Buffer::AsciiWrite);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "binaryWrite", Buffer::BinaryWrite);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "unpack", Buffer::Unpack);
|
||||
|
||||
NODE_SET_METHOD(constructor_template->GetFunction(),
|
||||
"byteLength",
|
||||
Buffer::ByteLength);
|
||||
|
||||
target->Set(String::NewSymbol("Buffer"), constructor_template->GetFunction());
|
||||
}
|
||||
|
||||
|
||||
} // namespace node
|
73
src/node_buffer.h
Normal file
73
src/node_buffer.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef NODE_BUFFER_H_
|
||||
#define NODE_BUFFER_H_
|
||||
|
||||
#include <node.h>
|
||||
#include <node_object_wrap.h>
|
||||
#include <v8.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
/* A buffer is a chunk of memory stored outside the V8 heap, mirrored by an
|
||||
* object in javascript. The object is not totally opaque, one can access
|
||||
* individual bytes with [] and slice it into substrings or sub-buffers
|
||||
* without copying memory.
|
||||
*
|
||||
* // return an ascii encoded string - no memory iscopied
|
||||
* buffer.asciiSlide(0, 3)
|
||||
*
|
||||
* // returns another buffer - no memory is copied
|
||||
* buffer.slice(0, 3)
|
||||
*
|
||||
* Interally, each javascript buffer object is backed by a "struct buffer"
|
||||
* object. These "struct buffer" objects are either a root buffer (in the
|
||||
* case that buffer->root == NULL) or slice objects (in which case
|
||||
* buffer->root != NULL). A root buffer is only GCed once all its slices
|
||||
* are GCed.
|
||||
*/
|
||||
|
||||
|
||||
struct Blob_;
|
||||
|
||||
class Buffer : public ObjectWrap {
|
||||
public:
|
||||
static void Initialize(v8::Handle<v8::Object> target);
|
||||
static inline bool HasInstance(v8::Handle<v8::Value> val) {
|
||||
if (!val->IsObject()) return false;
|
||||
v8::Local<v8::Object> obj = val->ToObject();
|
||||
return constructor_template->HasInstance(obj);
|
||||
}
|
||||
|
||||
const char* data() const { return data_; }
|
||||
size_t length() const { return length_; }
|
||||
struct Blob_* blob() const { return blob_; }
|
||||
|
||||
protected:
|
||||
static v8::Persistent<v8::FunctionTemplate> constructor_template;
|
||||
static v8::Handle<v8::Value> New(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> Slice(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> BinarySlice(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> AsciiSlice(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> Utf8Slice(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> BinaryWrite(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> AsciiWrite(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> Utf8Write(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> ByteLength(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> Unpack(const v8::Arguments &args);
|
||||
|
||||
int AsciiWrite(char *string, int offset, int length);
|
||||
int Utf8Write(char *string, int offset, int length);
|
||||
|
||||
private:
|
||||
Buffer(size_t length);
|
||||
Buffer(Buffer *parent, size_t start, size_t end);
|
||||
~Buffer();
|
||||
|
||||
const char *data_;
|
||||
size_t length_;
|
||||
struct Blob_ *blob_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace node buffer
|
||||
|
||||
#endif // NODE_BUFFER_H_
|
@ -15,56 +15,55 @@ namespace node {
|
||||
|
||||
using namespace v8;
|
||||
|
||||
Persistent<FunctionTemplate> ChildProcess::constructor_template;
|
||||
|
||||
static Persistent<String> pid_symbol;
|
||||
static Persistent<String> exit_symbol;
|
||||
static Persistent<String> output_symbol;
|
||||
static Persistent<String> error_symbol;
|
||||
static Persistent<String> onexit_symbol;
|
||||
|
||||
|
||||
// TODO share with other modules
|
||||
static inline int SetNonBlocking(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
if (r != 0) {
|
||||
perror("SetNonBlocking()");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void ChildProcess::Initialize(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(ChildProcess::New);
|
||||
constructor_template = Persistent<FunctionTemplate>::New(t);
|
||||
constructor_template->Inherit(EventEmitter::constructor_template);
|
||||
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
constructor_template->SetClassName(String::NewSymbol("ChildProcess"));
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(String::NewSymbol("ChildProcess"));
|
||||
|
||||
pid_symbol = NODE_PSYMBOL("pid");
|
||||
exit_symbol = NODE_PSYMBOL("exit");
|
||||
output_symbol = NODE_PSYMBOL("output");
|
||||
error_symbol = NODE_PSYMBOL("error");
|
||||
onexit_symbol = NODE_PSYMBOL("onexit");
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "spawn", ChildProcess::Spawn);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "write", ChildProcess::Write);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", ChildProcess::Close);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "kill", ChildProcess::Kill);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "spawn", ChildProcess::Spawn);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "kill", ChildProcess::Kill);
|
||||
|
||||
target->Set(String::NewSymbol("ChildProcess"),
|
||||
constructor_template->GetFunction());
|
||||
target->Set(String::NewSymbol("ChildProcess"), t->GetFunction());
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> ChildProcess::New(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
ChildProcess *p = new ChildProcess();
|
||||
p->Wrap(args.Holder());
|
||||
|
||||
return args.This();
|
||||
}
|
||||
|
||||
|
||||
// This is an internal function. The third argument should be an array
|
||||
// of key value pairs seperated with '='.
|
||||
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
if ( args.Length() != 3
|
||||
|| !args[0]->IsString()
|
||||
|| !args[1]->IsArray()
|
||||
|| !args[2]->IsArray()
|
||||
)
|
||||
{
|
||||
if (args.Length() != 3 ||
|
||||
!args[0]->IsString() ||
|
||||
!args[1]->IsArray() ||
|
||||
!args[2]->IsArray()) {
|
||||
return ThrowException(Exception::Error(String::New("Bad argument.")));
|
||||
}
|
||||
|
||||
@ -98,7 +97,9 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||||
env[i] = strdup(*pair);
|
||||
}
|
||||
|
||||
int r = child->Spawn(argv[0], argv, env);
|
||||
int fds[3];
|
||||
|
||||
int r = child->Spawn(argv[0], argv, env, fds);
|
||||
|
||||
for (i = 0; i < argv_length; i++) free(argv[i]);
|
||||
delete [] argv;
|
||||
@ -110,32 +111,18 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||||
return ThrowException(Exception::Error(String::New("Error spawning")));
|
||||
}
|
||||
|
||||
child->handle_->Set(pid_symbol, Integer::New(child->pid_));
|
||||
Local<Array> a = Array::New(3);
|
||||
|
||||
return Undefined();
|
||||
assert(fds[0] >= 0);
|
||||
a->Set(0, Integer::New(fds[0])); // stdin
|
||||
assert(fds[1] >= 0);
|
||||
a->Set(1, Integer::New(fds[1])); // stdout
|
||||
assert(fds[2] >= 0);
|
||||
a->Set(2, Integer::New(fds[2])); // stderr
|
||||
|
||||
return scope.Close(a);
|
||||
}
|
||||
|
||||
Handle<Value> ChildProcess::Write(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||||
assert(child);
|
||||
|
||||
enum encoding enc = ParseEncoding(args[1]);
|
||||
ssize_t len = DecodeBytes(args[0], enc);
|
||||
|
||||
if (len < 0) {
|
||||
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
||||
return ThrowException(exception);
|
||||
}
|
||||
|
||||
char * buf = new char[len];
|
||||
ssize_t written = DecodeWrite(buf, len, args[0], enc);
|
||||
assert(written == len);
|
||||
int r = child->Write(buf, len);
|
||||
delete [] buf;
|
||||
|
||||
return r == 0 ? True() : False();
|
||||
}
|
||||
|
||||
Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
@ -167,160 +154,59 @@ Handle<Value> ChildProcess::Kill(const Arguments& args) {
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
Handle<Value> ChildProcess::Close(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
ChildProcess *child = ObjectWrap::Unwrap<ChildProcess>(args.Holder());
|
||||
assert(child);
|
||||
return child->Close() == 0 ? True() : False();
|
||||
}
|
||||
|
||||
void ChildProcess::reader_closed(evcom_reader *r) {
|
||||
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
||||
if (r == &child->stdout_reader_) {
|
||||
child->stdout_fd_ = -1;
|
||||
} else {
|
||||
assert(r == &child->stderr_reader_);
|
||||
child->stderr_fd_ = -1;
|
||||
void ChildProcess::Stop() {
|
||||
if (ev_is_active(&child_watcher_)) {
|
||||
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
|
||||
Unref();
|
||||
}
|
||||
evcom_reader_detach(r);
|
||||
child->MaybeShutdown();
|
||||
// Don't kill the PID here. We want to allow for killing the parent
|
||||
// process and reparenting to initd. This is perhaps not going the best
|
||||
// technique for daemonizing, but I don't want to rule it out.
|
||||
pid_ = -1;
|
||||
}
|
||||
|
||||
void ChildProcess::stdin_closed(evcom_writer *w) {
|
||||
ChildProcess *child = static_cast<ChildProcess*>(w->data);
|
||||
assert(w == &child->stdin_writer_);
|
||||
child->stdin_fd_ = -1;
|
||||
evcom_writer_detach(w);
|
||||
child->MaybeShutdown();
|
||||
}
|
||||
|
||||
void ChildProcess::on_read(evcom_reader *r, const void *buf, size_t len) {
|
||||
ChildProcess *child = static_cast<ChildProcess*>(r->data);
|
||||
HandleScope scope;
|
||||
|
||||
bool isSTDOUT = (r == &child->stdout_reader_);
|
||||
enum encoding encoding = isSTDOUT ?
|
||||
child->stdout_encoding_ : child->stderr_encoding_;
|
||||
|
||||
// TODO emit 'end' event instead of null.
|
||||
|
||||
Local<Value> data = len ? Encode(buf, len, encoding) : Local<Value>::New(Null());
|
||||
child->Emit(isSTDOUT ? output_symbol : error_symbol, 1, &data);
|
||||
child->MaybeShutdown();
|
||||
}
|
||||
|
||||
ChildProcess::ChildProcess() : EventEmitter() {
|
||||
evcom_reader_init(&stdout_reader_);
|
||||
stdout_reader_.data = this;
|
||||
stdout_reader_.on_read = on_read;
|
||||
stdout_reader_.on_close = reader_closed;
|
||||
|
||||
evcom_reader_init(&stderr_reader_);
|
||||
stderr_reader_.data = this;
|
||||
stderr_reader_.on_read = on_read;
|
||||
stderr_reader_.on_close = reader_closed;
|
||||
|
||||
evcom_writer_init(&stdin_writer_);
|
||||
stdin_writer_.data = this;
|
||||
stdin_writer_.on_close = stdin_closed;
|
||||
|
||||
ev_init(&child_watcher_, ChildProcess::OnCHLD);
|
||||
child_watcher_.data = this;
|
||||
|
||||
stdout_fd_ = -1;
|
||||
stderr_fd_ = -1;
|
||||
stdin_fd_ = -1;
|
||||
|
||||
stdout_encoding_ = UTF8;
|
||||
stderr_encoding_ = UTF8;
|
||||
|
||||
got_chld_ = false;
|
||||
exit_code_ = 0;
|
||||
|
||||
pid_ = 0;
|
||||
}
|
||||
|
||||
ChildProcess::~ChildProcess() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void ChildProcess::Shutdown() {
|
||||
if (stdin_fd_ >= 0) {
|
||||
evcom_writer_close(&stdin_writer_);
|
||||
}
|
||||
|
||||
if (stdin_fd_ >= 0) close(stdin_fd_);
|
||||
if (stdout_fd_ >= 0) close(stdout_fd_);
|
||||
if (stderr_fd_ >= 0) close(stderr_fd_);
|
||||
|
||||
stdin_fd_ = -1;
|
||||
stdout_fd_ = -1;
|
||||
stderr_fd_ = -1;
|
||||
|
||||
evcom_writer_detach(&stdin_writer_);
|
||||
evcom_reader_detach(&stdout_reader_);
|
||||
evcom_reader_detach(&stderr_reader_);
|
||||
|
||||
ev_child_stop(EV_DEFAULT_UC_ &child_watcher_);
|
||||
|
||||
/* XXX Kill the PID? */
|
||||
pid_ = 0;
|
||||
}
|
||||
|
||||
static inline int SetNonBlocking(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
if (r != 0) {
|
||||
perror("SetNonBlocking()");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Note that args[0] must be the same as the "file" param. This is an
|
||||
// execvp() requirement.
|
||||
int ChildProcess::Spawn(const char *file, char *const args[], char **env) {
|
||||
assert(pid_ == 0);
|
||||
assert(stdout_fd_ == -1);
|
||||
assert(stderr_fd_ == -1);
|
||||
assert(stdin_fd_ == -1);
|
||||
//
|
||||
int ChildProcess::Spawn(const char *file,
|
||||
char *const args[],
|
||||
char **env,
|
||||
int stdio_fds[3]) {
|
||||
HandleScope scope;
|
||||
assert(pid_ == -1);
|
||||
assert(!ev_is_active(&child_watcher_));
|
||||
|
||||
int stdout_pipe[2], stdin_pipe[2], stderr_pipe[2];
|
||||
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
||||
|
||||
/* An implementation of popen(), basically */
|
||||
if (pipe(stdout_pipe) < 0) {
|
||||
if (pipe(stdin_pipe) < 0 ||
|
||||
pipe(stdout_pipe) < 0 ||
|
||||
pipe(stderr_pipe) < 0) {
|
||||
perror("pipe()");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pipe(stderr_pipe) < 0) {
|
||||
perror("pipe()");
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (pipe(stdin_pipe) < 0) {
|
||||
perror("pipe()");
|
||||
return -3;
|
||||
}
|
||||
|
||||
// Save environ in the case that we get it clobbered
|
||||
// by the child process.
|
||||
char **save_our_env = environ;
|
||||
|
||||
switch (pid_ = vfork()) {
|
||||
case -1: // Error.
|
||||
Shutdown();
|
||||
Stop();
|
||||
return -4;
|
||||
|
||||
case 0: // Child.
|
||||
close(stdin_pipe[1]); // close write end
|
||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||
|
||||
close(stdout_pipe[0]); // close read end
|
||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||
|
||||
close(stderr_pipe[0]); // close read end
|
||||
dup2(stderr_pipe[1], STDERR_FILENO);
|
||||
|
||||
close(stdin_pipe[1]); // close write end
|
||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||
|
||||
environ = env;
|
||||
|
||||
execvp(file, args);
|
||||
@ -328,81 +214,59 @@ int ChildProcess::Spawn(const char *file, char *const args[], char **env) {
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
// Parent.
|
||||
|
||||
// Restore environment.
|
||||
environ = save_our_env;
|
||||
|
||||
// Parent.
|
||||
|
||||
ev_child_set(&child_watcher_, pid_, 0);
|
||||
ev_child_start(EV_DEFAULT_UC_ &child_watcher_);
|
||||
|
||||
close(stdout_pipe[1]);
|
||||
stdout_fd_ = stdout_pipe[0];
|
||||
SetNonBlocking(stdout_fd_);
|
||||
|
||||
close(stderr_pipe[1]);
|
||||
stderr_fd_ = stderr_pipe[0];
|
||||
SetNonBlocking(stderr_fd_);
|
||||
Ref();
|
||||
handle_->Set(pid_symbol, Integer::New(pid_));
|
||||
|
||||
close(stdin_pipe[0]);
|
||||
stdin_fd_ = stdin_pipe[1];
|
||||
SetNonBlocking(stdin_fd_);
|
||||
stdio_fds[0] = stdin_pipe[1];
|
||||
SetNonBlocking(stdin_pipe[1]);
|
||||
|
||||
evcom_reader_set(&stdout_reader_, stdout_fd_);
|
||||
evcom_reader_attach(EV_DEFAULT_UC_ &stdout_reader_);
|
||||
close(stdout_pipe[1]);
|
||||
stdio_fds[1] = stdout_pipe[0];
|
||||
SetNonBlocking(stdout_pipe[0]);
|
||||
|
||||
evcom_reader_set(&stderr_reader_, stderr_fd_);
|
||||
evcom_reader_attach(EV_DEFAULT_UC_ &stderr_reader_);
|
||||
|
||||
evcom_writer_set(&stdin_writer_, stdin_fd_);
|
||||
evcom_writer_attach(EV_DEFAULT_UC_ &stdin_writer_);
|
||||
|
||||
Ref();
|
||||
close(stderr_pipe[1]);
|
||||
stdio_fds[2] = stderr_pipe[0];
|
||||
SetNonBlocking(stderr_pipe[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChildProcess::OnCHLD(EV_P_ ev_child *watcher, int revents) {
|
||||
ev_child_stop(EV_A_ watcher);
|
||||
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
|
||||
|
||||
assert(revents == EV_CHILD);
|
||||
assert(child->pid_ == watcher->rpid);
|
||||
assert(&child->child_watcher_ == watcher);
|
||||
void ChildProcess::OnExit(int code) {
|
||||
HandleScope scope;
|
||||
|
||||
child->got_chld_ = true;
|
||||
child->exit_code_ = watcher->rstatus;
|
||||
pid_ = -1;
|
||||
Stop();
|
||||
|
||||
if (child->stdin_fd_ >= 0) evcom_writer_close(&child->stdin_writer_);
|
||||
handle_->Set(pid_symbol, Null());
|
||||
|
||||
child->MaybeShutdown();
|
||||
}
|
||||
Local<Value> onexit_v = handle_->Get(onexit_symbol);
|
||||
assert(onexit_v->IsFunction());
|
||||
Local<Function> onexit = Local<Function>::Cast(onexit_v);
|
||||
|
||||
int ChildProcess::Write(const char *str, size_t len) {
|
||||
if (stdin_fd_ < 0 || got_chld_) return -1;
|
||||
evcom_writer_write(&stdin_writer_, str, len);
|
||||
return 0;
|
||||
}
|
||||
TryCatch try_catch;
|
||||
|
||||
int ChildProcess::Close(void) {
|
||||
if (stdin_fd_ < 0 || got_chld_) return -1;
|
||||
evcom_writer_close(&stdin_writer_);
|
||||
return 0;
|
||||
}
|
||||
Local<Value> argv[1];
|
||||
argv[0] = Integer::New(code);
|
||||
|
||||
int ChildProcess::Kill(int sig) {
|
||||
if (got_chld_ || pid_ == 0) return -1;
|
||||
return kill(pid_, sig);
|
||||
}
|
||||
onexit->Call(handle_, 1, argv);
|
||||
|
||||
void ChildProcess::MaybeShutdown(void) {
|
||||
if (stdout_fd_ < 0 && stderr_fd_ < 0 && got_chld_) {
|
||||
HandleScope scope;
|
||||
Handle<Value> argv[1] = { Integer::New(exit_code_) };
|
||||
Emit(exit_symbol, 1, argv);
|
||||
Shutdown();
|
||||
Unref();
|
||||
if (try_catch.HasCaught()) {
|
||||
FatalException(try_catch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int ChildProcess::Kill(int sig) {
|
||||
return kill(pid_, sig);
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
|
@ -1,65 +1,68 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#ifndef SRC_CHILD_PROCESS_H_
|
||||
#define SRC_CHILD_PROCESS_H_
|
||||
#ifndef NODE_CHILD_PROCESS_H_
|
||||
#define NODE_CHILD_PROCESS_H_
|
||||
|
||||
#include <node.h>
|
||||
#include <node_events.h>
|
||||
|
||||
#include <node_object_wrap.h>
|
||||
#include <v8.h>
|
||||
#include <ev.h>
|
||||
#include <evcom.h>
|
||||
|
||||
// ChildProcess is a thin wrapper around ev_child. It has the extra
|
||||
// functionality that it can spawn a child process with pipes connected to
|
||||
// its stdin, stdout, stderr. This class is not meant to be exposed to but
|
||||
// wrapped up in a more friendly EventEmitter with streams for each of the
|
||||
// pipes.
|
||||
//
|
||||
// When the child process exits (when the parent receives SIGCHLD) the
|
||||
// callback child.onexit will be called.
|
||||
|
||||
namespace node {
|
||||
|
||||
class ChildProcess : EventEmitter {
|
||||
class ChildProcess : ObjectWrap {
|
||||
public:
|
||||
static void Initialize(v8::Handle<v8::Object> target);
|
||||
|
||||
protected:
|
||||
static v8::Persistent<v8::FunctionTemplate> constructor_template;
|
||||
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Spawn(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Write(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Kill(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> PIDGetter(v8::Local<v8::String> _,
|
||||
const v8::AccessorInfo& info);
|
||||
|
||||
ChildProcess();
|
||||
~ChildProcess();
|
||||
ChildProcess() : ObjectWrap() {
|
||||
ev_init(&child_watcher_, ChildProcess::on_chld);
|
||||
child_watcher_.data = this;
|
||||
pid_ = -1;
|
||||
}
|
||||
|
||||
int Spawn(const char *file, char *const argv[], char **env);
|
||||
int Write(const char *str, size_t len);
|
||||
int Close(void);
|
||||
~ChildProcess() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
// Returns 0 on success. stdio_fds will contain file desciptors for stdin,
|
||||
// stdout, and stderr of the subprocess. stdin is writable; the other two
|
||||
// are readable.
|
||||
// The user of this class has responsibility to close these pipes after
|
||||
// the child process exits.
|
||||
int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3]);
|
||||
|
||||
// Simple syscall wrapper. Does not disable the watcher. onexit will be
|
||||
// called still.
|
||||
int Kill(int sig);
|
||||
|
||||
private:
|
||||
static void on_read(evcom_reader *r, const void *buf, size_t len);
|
||||
static void reader_closed(evcom_reader *r);
|
||||
static void stdin_closed(evcom_writer *w);
|
||||
static void OnCHLD(EV_P_ ev_child *watcher, int revents);
|
||||
void OnExit(int code);
|
||||
void Stop(void);
|
||||
|
||||
void MaybeShutdown(void);
|
||||
void Shutdown(void);
|
||||
|
||||
evcom_reader stdout_reader_;
|
||||
evcom_reader stderr_reader_;
|
||||
evcom_writer stdin_writer_;
|
||||
static void on_chld(EV_P_ ev_child *watcher, int revents) {
|
||||
ChildProcess *child = static_cast<ChildProcess*>(watcher->data);
|
||||
assert(revents == EV_CHILD);
|
||||
assert(child->pid_ == watcher->rpid);
|
||||
assert(&child->child_watcher_ == watcher);
|
||||
child->OnExit(watcher->rstatus);
|
||||
}
|
||||
|
||||
ev_child child_watcher_;
|
||||
|
||||
int stdout_fd_;
|
||||
int stderr_fd_;
|
||||
int stdin_fd_;
|
||||
|
||||
enum encoding stdout_encoding_;
|
||||
enum encoding stderr_encoding_;
|
||||
|
||||
pid_t pid_;
|
||||
|
||||
bool got_chld_;
|
||||
int exit_code_;
|
||||
};
|
||||
|
||||
} // namespace node
|
||||
#endif // SRC_CHILD_PROCESS_H_
|
||||
#endif // NODE_CHILD_PROCESS_H_
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#include <node_dns.h>
|
||||
#include <node.h>
|
||||
|
||||
#include <stdlib.h> /* exit() */
|
||||
#include <sys/types.h>
|
||||
@ -26,24 +27,6 @@ static Persistent<String> weight_symbol;
|
||||
static Persistent<String> port_symbol;
|
||||
static Persistent<String> name_symbol;
|
||||
|
||||
static inline Persistent<Function>* cb_persist(const Local<Value> &v) {
|
||||
Persistent<Function> *fn = new Persistent<Function>();
|
||||
*fn = Persistent<Function>::New(Local<Function>::Cast(v));
|
||||
return fn;
|
||||
}
|
||||
|
||||
static inline Persistent<Function>* cb_unwrap(void *data) {
|
||||
Persistent<Function> *cb =
|
||||
reinterpret_cast<Persistent<Function>*>(data);
|
||||
assert((*cb)->IsFunction());
|
||||
return cb;
|
||||
}
|
||||
|
||||
static inline void cb_destroy(Persistent<Function> * cb) {
|
||||
cb->Dispose();
|
||||
delete cb;
|
||||
}
|
||||
|
||||
static inline void set_timeout() {
|
||||
int maxwait = 20;
|
||||
int wait = dns_timeouts(NULL, maxwait, ev_now(EV_DEFAULT_UC));
|
||||
|
141
src/node_file.cc
141
src/node_file.cc
@ -1,5 +1,6 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#include <node_file.h>
|
||||
#include <node_buffer.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@ -37,9 +38,7 @@ static inline Local<Value> errno_exception(int errorno) {
|
||||
static int After(eio_req *req) {
|
||||
HandleScope scope;
|
||||
|
||||
Persistent<Function> *callback =
|
||||
reinterpret_cast<Persistent<Function>*>(req->data);
|
||||
assert((*callback)->IsFunction());
|
||||
Persistent<Function> *callback = cb_unwrap(req->data);
|
||||
|
||||
ev_unref(EV_DEFAULT_UC);
|
||||
|
||||
@ -86,7 +85,7 @@ static int After(eio_req *req) {
|
||||
argv[1] = BuildStatsObject(s);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case EIO_READLINK:
|
||||
{
|
||||
argc = 2;
|
||||
@ -133,7 +132,7 @@ static int After(eio_req *req) {
|
||||
}
|
||||
}
|
||||
|
||||
if (req->type == EIO_WRITE) {
|
||||
if (req->type == EIO_WRITE && req->int3 == 1) {
|
||||
assert(req->ptr2);
|
||||
delete [] reinterpret_cast<char*>(req->ptr2);
|
||||
}
|
||||
@ -147,21 +146,14 @@ static int After(eio_req *req) {
|
||||
}
|
||||
|
||||
// Dispose of the persistent handle
|
||||
callback->Dispose();
|
||||
delete callback;
|
||||
cb_destroy(callback);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Persistent<Function>* persistent_callback(const Local<Value> &v) {
|
||||
Persistent<Function> *fn = new Persistent<Function>();
|
||||
*fn = Persistent<Function>::New(Local<Function>::Cast(v));
|
||||
return fn;
|
||||
}
|
||||
|
||||
#define ASYNC_CALL(func, callback, ...) \
|
||||
eio_req *req = eio_##func(__VA_ARGS__, EIO_PRI_DEFAULT, After, \
|
||||
persistent_callback(callback)); \
|
||||
cb_persist(callback)); \
|
||||
assert(req); \
|
||||
ev_ref(EV_DEFAULT_UC); \
|
||||
return Undefined();
|
||||
@ -456,43 +448,118 @@ static Handle<Value> Open(const Arguments& args) {
|
||||
}
|
||||
}
|
||||
|
||||
/* node.fs.write(fd, data, position, enc, callback)
|
||||
* Wrapper for write(2).
|
||||
*
|
||||
* 0 fd integer. file descriptor
|
||||
* 1 data the data to write (string = utf8, array = raw)
|
||||
* 2 position if integer, position to write at in the file.
|
||||
* if null, write from the current position
|
||||
* 3 encoding
|
||||
*/
|
||||
// write(fd, data, position, enc, callback)
|
||||
// Wrapper for write(2).
|
||||
//
|
||||
// 0 fd integer. file descriptor
|
||||
// 1 buffer the data to write
|
||||
// 2 offset where in the buffer to start from
|
||||
// 3 length how much to write
|
||||
// 4 position if integer, position to write at in the file.
|
||||
// if null, write from the current position
|
||||
//
|
||||
// - OR -
|
||||
//
|
||||
// 0 fd integer. file descriptor
|
||||
// 1 string the data to write
|
||||
// 2 position if integer, position to write at in the file.
|
||||
// if null, write from the current position
|
||||
// 3 encoding
|
||||
static Handle<Value> Write(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
if (args.Length() < 4 || !args[0]->IsInt32()) {
|
||||
if (!args[0]->IsInt32()) {
|
||||
return THROW_BAD_ARGS;
|
||||
}
|
||||
|
||||
int fd = args[0]->Int32Value();
|
||||
off_t offset = args[2]->IsNumber() ? args[2]->IntegerValue() : -1;
|
||||
|
||||
enum encoding enc = ParseEncoding(args[3]);
|
||||
ssize_t len = DecodeBytes(args[1], enc);
|
||||
if (len < 0) {
|
||||
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
||||
return ThrowException(exception);
|
||||
off_t pos;
|
||||
ssize_t len;
|
||||
char * buf;
|
||||
ssize_t written;
|
||||
|
||||
Local<Value> cb;
|
||||
bool legacy;
|
||||
|
||||
if (Buffer::HasInstance(args[1])) {
|
||||
legacy = false;
|
||||
// buffer
|
||||
//
|
||||
// 0 fd integer. file descriptor
|
||||
// 1 buffer the data to write
|
||||
// 2 offset where in the buffer to start from
|
||||
// 3 length how much to write
|
||||
// 4 position if integer, position to write at in the file.
|
||||
// if null, write from the current position
|
||||
|
||||
Buffer * buffer = ObjectWrap::Unwrap<Buffer>(args[1]->ToObject());
|
||||
|
||||
size_t off = args[2]->Int32Value();
|
||||
if (off >= buffer->length()) {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Offset is out of bounds")));
|
||||
}
|
||||
|
||||
len = args[3]->Int32Value();
|
||||
if (off + len > buffer->length()) {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Length is extends beyond buffer")));
|
||||
}
|
||||
|
||||
pos = args[4]->IsNumber() ? args[4]->IntegerValue() : -1;
|
||||
|
||||
buf = (char*)buffer->data() + off;
|
||||
|
||||
cb = args[5];
|
||||
|
||||
} else {
|
||||
legacy = true;
|
||||
// legacy interface.. args[1] is a string
|
||||
|
||||
pos = args[2]->IsNumber() ? args[2]->IntegerValue() : -1;
|
||||
|
||||
enum encoding enc = ParseEncoding(args[3]);
|
||||
|
||||
len = DecodeBytes(args[1], enc);
|
||||
if (len < 0) {
|
||||
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
||||
return ThrowException(exception);
|
||||
}
|
||||
|
||||
buf = new char[len];
|
||||
written = DecodeWrite(buf, len, args[1], enc);
|
||||
assert(written == len);
|
||||
|
||||
cb = args[4];
|
||||
}
|
||||
|
||||
char * buf = new char[len];
|
||||
ssize_t written = DecodeWrite(buf, len, args[1], enc);
|
||||
assert(written == len);
|
||||
if (cb->IsFunction()) {
|
||||
// WARNING: HACK AHEAD, PROCEED WITH CAUTION
|
||||
// Normally here I would do
|
||||
// ASYNC_CALL(write, cb, fd, buf, len, pos)
|
||||
// however, I'm trying to support a legacy interface; where in the
|
||||
// String version a buffer is allocated to encode into. In the After()
|
||||
// function it is freed. However in the other version, we just increase
|
||||
// the reference count to a buffer. We have to let After() know which
|
||||
// version is being done so it can know if it needs to free 'buf' or
|
||||
// not. We do that by using req->int3.
|
||||
// req->int3 == 1 legacy, String version. Need to free `buf`.
|
||||
// req->int3 == 0 Buffer version. Don't do anything.
|
||||
eio_req *req = eio_write(fd, buf, len, pos,
|
||||
EIO_PRI_DEFAULT,
|
||||
After,
|
||||
cb_persist(cb));
|
||||
assert(req);
|
||||
req->int3 = legacy ? 1 : 0;
|
||||
ev_ref(EV_DEFAULT_UC);
|
||||
return Undefined();
|
||||
|
||||
if (args[4]->IsFunction()) {
|
||||
ASYNC_CALL(write, args[4], fd, buf, len, offset)
|
||||
} else {
|
||||
if (offset < 0) {
|
||||
if (pos < 0) {
|
||||
written = write(fd, buf, len);
|
||||
} else {
|
||||
written = pwrite(fd, buf, len, offset);
|
||||
written = pwrite(fd, buf, len, pos);
|
||||
}
|
||||
if (written < 0) return ThrowException(errno_exception(errno));
|
||||
return scope.Close(Integer::New(written));
|
||||
|
362
src/node_http_parser.cc
Normal file
362
src/node_http_parser.cc
Normal file
@ -0,0 +1,362 @@
|
||||
#include <node_http_parser.h>
|
||||
|
||||
#include <v8.h>
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
|
||||
#include <http_parser.h>
|
||||
|
||||
#include <strings.h> /* strcasecmp() */
|
||||
|
||||
// This is a binding to http_parser (http://github.com/ry/http-parser)
|
||||
// The goal is to decouple sockets from parsing for more javascript-level
|
||||
// agility. A Buffer is read from a socket and passed to parser.execute().
|
||||
// The parser then issues callbacks with slices of the data
|
||||
// parser.onMessageBegin
|
||||
// parser.onPath
|
||||
// parser.onBody
|
||||
// ...
|
||||
// No copying is performed when slicing the buffer, only small reference
|
||||
// allocations.
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
using namespace v8;
|
||||
|
||||
static int deep = 0;
|
||||
|
||||
static Persistent<String> on_message_begin_sym;
|
||||
static Persistent<String> on_path_sym;
|
||||
static Persistent<String> on_query_string_sym;
|
||||
static Persistent<String> on_url_sym;
|
||||
static Persistent<String> on_fragment_sym;
|
||||
static Persistent<String> on_header_field_sym;
|
||||
static Persistent<String> on_header_value_sym;
|
||||
static Persistent<String> on_headers_complete_sym;
|
||||
static Persistent<String> on_body_sym;
|
||||
static Persistent<String> on_message_complete_sym;
|
||||
|
||||
static Persistent<String> delete_sym;
|
||||
static Persistent<String> get_sym;
|
||||
static Persistent<String> head_sym;
|
||||
static Persistent<String> post_sym;
|
||||
static Persistent<String> put_sym;
|
||||
static Persistent<String> connect_sym;
|
||||
static Persistent<String> options_sym;
|
||||
static Persistent<String> trace_sym;
|
||||
static Persistent<String> copy_sym;
|
||||
static Persistent<String> lock_sym;
|
||||
static Persistent<String> mkcol_sym;
|
||||
static Persistent<String> move_sym;
|
||||
static Persistent<String> propfind_sym;
|
||||
static Persistent<String> proppatch_sym;
|
||||
static Persistent<String> unlock_sym;
|
||||
static Persistent<String> unknown_method_sym;
|
||||
|
||||
static Persistent<String> method_sym;
|
||||
static Persistent<String> status_code_sym;
|
||||
static Persistent<String> http_version_sym;
|
||||
static Persistent<String> version_major_sym;
|
||||
static Persistent<String> version_minor_sym;
|
||||
static Persistent<String> should_keep_alive_sym;
|
||||
|
||||
// Callback prototype for http_cb
|
||||
#define DEFINE_HTTP_CB(name) \
|
||||
static int name(http_parser *p) { \
|
||||
Parser *parser = static_cast<Parser*>(p->data); \
|
||||
Local<Value> cb_value = parser->handle_->Get(name##_sym); \
|
||||
if (!cb_value->IsFunction()) return 0; \
|
||||
Local<Function> cb = Local<Function>::Cast(cb_value); \
|
||||
Local<Value> ret = cb->Call(parser->handle_, 0, NULL); \
|
||||
return ret.IsEmpty() ? -1 : 0; \
|
||||
}
|
||||
|
||||
// Callback prototype for http_data_cb
|
||||
#define DEFINE_HTTP_DATA_CB(name) \
|
||||
static int name(http_parser *p, const char *at, size_t length) { \
|
||||
Parser *parser = static_cast<Parser*>(p->data); \
|
||||
assert(parser->buffer_); \
|
||||
Local<Value> cb_value = parser->handle_->Get(name##_sym); \
|
||||
if (!cb_value->IsFunction()) return 0; \
|
||||
Local<Function> cb = Local<Function>::Cast(cb_value); \
|
||||
Local<Value> argv[3] = { Local<Value>::New(parser->buffer_->handle_) \
|
||||
, Integer::New(at - parser->buffer_->data()) \
|
||||
, Integer::New(length) \
|
||||
}; \
|
||||
Local<Value> ret = cb->Call(parser->handle_, 3, argv); \
|
||||
assert(parser->buffer_); \
|
||||
return ret.IsEmpty() ? -1 : 0; \
|
||||
}
|
||||
|
||||
|
||||
static inline Persistent<String>
|
||||
method_to_str(enum http_method m) {
|
||||
switch (m) {
|
||||
case HTTP_DELETE: return delete_sym;
|
||||
case HTTP_GET: return get_sym;
|
||||
case HTTP_HEAD: return head_sym;
|
||||
case HTTP_POST: return post_sym;
|
||||
case HTTP_PUT: return put_sym;
|
||||
case HTTP_CONNECT: return connect_sym;
|
||||
case HTTP_OPTIONS: return options_sym;
|
||||
case HTTP_TRACE: return trace_sym;
|
||||
case HTTP_COPY: return copy_sym;
|
||||
case HTTP_LOCK: return lock_sym;
|
||||
case HTTP_MKCOL: return mkcol_sym;
|
||||
case HTTP_MOVE: return move_sym;
|
||||
case HTTP_PROPFIND: return propfind_sym;
|
||||
case HTTP_PROPPATCH: return proppatch_sym;
|
||||
case HTTP_UNLOCK: return unlock_sym;
|
||||
default: return unknown_method_sym;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Parser : public ObjectWrap {
|
||||
public:
|
||||
Parser(enum http_parser_type type) : ObjectWrap() {
|
||||
buffer_ = NULL;
|
||||
Init(type);
|
||||
}
|
||||
|
||||
~Parser() {
|
||||
assert(buffer_ == NULL && "Destroying a parser while it's parsing");
|
||||
}
|
||||
|
||||
DEFINE_HTTP_CB(on_message_begin)
|
||||
DEFINE_HTTP_CB(on_message_complete)
|
||||
|
||||
DEFINE_HTTP_DATA_CB(on_path)
|
||||
DEFINE_HTTP_DATA_CB(on_url)
|
||||
DEFINE_HTTP_DATA_CB(on_fragment)
|
||||
DEFINE_HTTP_DATA_CB(on_query_string)
|
||||
DEFINE_HTTP_DATA_CB(on_header_field)
|
||||
DEFINE_HTTP_DATA_CB(on_header_value)
|
||||
DEFINE_HTTP_DATA_CB(on_body)
|
||||
|
||||
static int on_headers_complete(http_parser *p) {
|
||||
Parser *parser = static_cast<Parser*>(p->data);
|
||||
|
||||
Local<Value> cb_value = parser->handle_->Get(on_headers_complete_sym);
|
||||
if (!cb_value->IsFunction()) return 0;
|
||||
Local<Function> cb = Local<Function>::Cast(cb_value);
|
||||
|
||||
|
||||
Local<Object> message_info = Object::New();
|
||||
|
||||
// METHOD
|
||||
if (p->type == HTTP_REQUEST) {
|
||||
message_info->Set(method_sym, method_to_str(p->method));
|
||||
}
|
||||
|
||||
// STATUS
|
||||
if (p->type == HTTP_RESPONSE) {
|
||||
message_info->Set(status_code_sym, Integer::New(p->status_code));
|
||||
}
|
||||
|
||||
// VERSION
|
||||
message_info->Set(version_major_sym, Integer::New(p->http_major));
|
||||
message_info->Set(version_minor_sym, Integer::New(p->http_minor));
|
||||
|
||||
message_info->Set(should_keep_alive_sym,
|
||||
http_should_keep_alive(p) ? True() : False());
|
||||
|
||||
Local<Value> argv[1] = { message_info };
|
||||
|
||||
Local<Value> ret = cb->Call(parser->handle_, 1, argv);
|
||||
|
||||
return ret.IsEmpty() ? -1 : 0;
|
||||
}
|
||||
|
||||
static Handle<Value> New(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
String::Utf8Value type(args[0]->ToString());
|
||||
|
||||
Parser *parser;
|
||||
|
||||
if (0 == strcasecmp(*type, "request")) {
|
||||
parser = new Parser(HTTP_REQUEST);
|
||||
} else if (0 == strcasecmp(*type, "response")) {
|
||||
parser = new Parser(HTTP_RESPONSE);
|
||||
} else {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Constructor argument be 'request' or 'response'")));
|
||||
}
|
||||
|
||||
parser->Wrap(args.This());
|
||||
assert(!parser->buffer_);
|
||||
|
||||
return args.This();
|
||||
}
|
||||
|
||||
// var bytesParsed = parser->execute(buffer, off, len);
|
||||
static Handle<Value> Execute(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
|
||||
|
||||
assert(!parser->buffer_);
|
||||
if (parser->buffer_) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("Already parsing a buffer")));
|
||||
}
|
||||
|
||||
if (!Buffer::HasInstance(args[0])) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("Argument should be a buffer")));
|
||||
}
|
||||
|
||||
Buffer * buffer = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());
|
||||
|
||||
size_t off = args[1]->Int32Value();
|
||||
if (off >= buffer->length()) {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Offset is out of bounds")));
|
||||
}
|
||||
|
||||
size_t len = args[2]->Int32Value();
|
||||
if (off+len > buffer->length()) {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Length is extends beyond buffer")));
|
||||
}
|
||||
|
||||
TryCatch try_catch;
|
||||
|
||||
// Assign 'buffer_' while we parse. The callbacks will access that varible.
|
||||
parser->buffer_ = buffer;
|
||||
|
||||
size_t nparsed =
|
||||
http_parser_execute(&parser->parser_, buffer->data()+off, len);
|
||||
|
||||
// Unassign the 'buffer_' variable
|
||||
assert(parser->buffer_);
|
||||
parser->buffer_ = NULL;
|
||||
|
||||
// If there was an exception in one of the callbacks
|
||||
if (try_catch.HasCaught()) return try_catch.ReThrow();
|
||||
|
||||
Local<Integer> nparsed_obj = Integer::New(nparsed);
|
||||
// If there was a parse error in one of the callbacks
|
||||
// TODO What if there is an error on EOF?
|
||||
if (nparsed != len) {
|
||||
Local<Value> e = Exception::Error(String::New("Parse Error"));
|
||||
Local<Object> obj = e->ToObject();
|
||||
obj->Set(String::NewSymbol("bytesParsed"), nparsed_obj);
|
||||
return ThrowException(e);
|
||||
}
|
||||
|
||||
assert(!parser->buffer_);
|
||||
return scope.Close(nparsed_obj);
|
||||
}
|
||||
|
||||
static Handle<Value> Finish(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
|
||||
|
||||
assert(!parser->buffer_);
|
||||
|
||||
http_parser_execute(&(parser->parser_), NULL, 0);
|
||||
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
static Handle<Value> Reinitialize(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
|
||||
|
||||
String::Utf8Value type(args[0]->ToString());
|
||||
|
||||
if (0 == strcasecmp(*type, "request")) {
|
||||
parser->Init(HTTP_REQUEST);
|
||||
} else if (0 == strcasecmp(*type, "response")) {
|
||||
parser->Init(HTTP_RESPONSE);
|
||||
} else {
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Argument be 'request' or 'response'")));
|
||||
}
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void Init (enum http_parser_type type) {
|
||||
assert(buffer_ == NULL); // don't call this during Execute()
|
||||
http_parser_init(&parser_, type);
|
||||
|
||||
parser_.on_message_begin = on_message_begin;
|
||||
parser_.on_path = on_path;
|
||||
parser_.on_query_string = on_query_string;
|
||||
parser_.on_url = on_url;
|
||||
parser_.on_fragment = on_fragment;
|
||||
parser_.on_header_field = on_header_field;
|
||||
parser_.on_header_value = on_header_value;
|
||||
parser_.on_headers_complete = on_headers_complete;
|
||||
parser_.on_body = on_body;
|
||||
parser_.on_message_complete = on_message_complete;
|
||||
|
||||
parser_.data = this;
|
||||
}
|
||||
|
||||
Buffer * buffer_; // The buffer currently being parsed.
|
||||
http_parser parser_;
|
||||
};
|
||||
|
||||
|
||||
void InitHttpParser(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(Parser::New);
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(String::NewSymbol("HTTPParser"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "execute", Parser::Execute);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "finish", Parser::Finish);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "reinitialize", Parser::Reinitialize);
|
||||
|
||||
target->Set(String::NewSymbol("HTTPParser"), t->GetFunction());
|
||||
|
||||
on_message_begin_sym = NODE_PSYMBOL("onMessageBegin");
|
||||
on_path_sym = NODE_PSYMBOL("onPath");
|
||||
on_query_string_sym = NODE_PSYMBOL("onQueryString");
|
||||
on_url_sym = NODE_PSYMBOL("onURL");
|
||||
on_fragment_sym = NODE_PSYMBOL("onFragment");
|
||||
on_header_field_sym = NODE_PSYMBOL("onHeaderField");
|
||||
on_header_value_sym = NODE_PSYMBOL("onHeaderValue");
|
||||
on_headers_complete_sym = NODE_PSYMBOL("onHeadersComplete");
|
||||
on_body_sym = NODE_PSYMBOL("onBody");
|
||||
on_message_complete_sym = NODE_PSYMBOL("onMessageComplete");
|
||||
|
||||
delete_sym = NODE_PSYMBOL("DELETE");
|
||||
get_sym = NODE_PSYMBOL("GET");
|
||||
head_sym = NODE_PSYMBOL("HEAD");
|
||||
post_sym = NODE_PSYMBOL("POST");
|
||||
put_sym = NODE_PSYMBOL("PUT");
|
||||
connect_sym = NODE_PSYMBOL("CONNECT");
|
||||
options_sym = NODE_PSYMBOL("OPTIONS");
|
||||
trace_sym = NODE_PSYMBOL("TRACE");
|
||||
copy_sym = NODE_PSYMBOL("COPY");
|
||||
lock_sym = NODE_PSYMBOL("LOCK");
|
||||
mkcol_sym = NODE_PSYMBOL("MKCOL");
|
||||
move_sym = NODE_PSYMBOL("MOVE");
|
||||
propfind_sym = NODE_PSYMBOL("PROPFIND");
|
||||
proppatch_sym = NODE_PSYMBOL("PROPPATCH");
|
||||
unlock_sym = NODE_PSYMBOL("UNLOCK");
|
||||
unknown_method_sym = NODE_PSYMBOL("UNKNOWN_METHOD");
|
||||
|
||||
method_sym = NODE_PSYMBOL("method");
|
||||
status_code_sym = NODE_PSYMBOL("statusCode");
|
||||
http_version_sym = NODE_PSYMBOL("httpVersion");
|
||||
version_major_sym = NODE_PSYMBOL("versionMajor");
|
||||
version_minor_sym = NODE_PSYMBOL("versionMinor");
|
||||
should_keep_alive_sym = NODE_PSYMBOL("shouldKeepAlive");
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
|
12
src/node_http_parser.h
Normal file
12
src/node_http_parser.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef NODE_HTTP_PARSER
|
||||
#define NODE_HTTP_PARSER
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
void InitHttpParser(v8::Handle<v8::Object> target);
|
||||
|
||||
}
|
||||
|
||||
#endif // NODE_HTTP_PARSER
|
@ -11,7 +11,7 @@ namespace node {
|
||||
using namespace v8;
|
||||
|
||||
Persistent<FunctionTemplate> IdleWatcher::constructor_template;
|
||||
Persistent<String> callback_symbol;
|
||||
static Persistent<String> callback_symbol;
|
||||
|
||||
void IdleWatcher::Initialize(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
145
src/node_io_watcher.cc
Normal file
145
src/node_io_watcher.cc
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#include <node_io_watcher.h>
|
||||
|
||||
#include <node.h>
|
||||
#include <v8.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
using namespace v8;
|
||||
|
||||
Persistent<FunctionTemplate> IOWatcher::constructor_template;
|
||||
Persistent<String> callback_symbol;
|
||||
|
||||
|
||||
void IOWatcher::Initialize(Handle<Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(IOWatcher::New);
|
||||
constructor_template = Persistent<FunctionTemplate>::New(t);
|
||||
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
constructor_template->SetClassName(String::NewSymbol("IOWatcher"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", IOWatcher::Start);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", IOWatcher::Stop);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "set", IOWatcher::Set);
|
||||
|
||||
target->Set(String::NewSymbol("IOWatcher"), constructor_template->GetFunction());
|
||||
|
||||
callback_symbol = NODE_PSYMBOL("callback");
|
||||
}
|
||||
|
||||
|
||||
void IOWatcher::Callback(EV_P_ ev_io *w, int revents) {
|
||||
IOWatcher *io = static_cast<IOWatcher*>(w->data);
|
||||
assert(w == &io->watcher_);
|
||||
HandleScope scope;
|
||||
|
||||
Local<Value> callback_v = io->handle_->Get(callback_symbol);
|
||||
if (!callback_v->IsFunction()) {
|
||||
io->Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Function> callback = Local<Function>::Cast(callback_v);
|
||||
|
||||
TryCatch try_catch;
|
||||
|
||||
Local<Value> argv[2];
|
||||
argv[0] = Local<Value>::New(revents & EV_READ ? True() : False());
|
||||
argv[1] = Local<Value>::New(revents & EV_WRITE ? True() : False());
|
||||
|
||||
io->Ref();
|
||||
callback->Call(io->handle_, 2, argv);
|
||||
io->Unref();
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
FatalException(try_catch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// var io = new process.IOWatcher();
|
||||
// process.callback = function (readable, writable) { ... };
|
||||
// io.set(fd, true, false);
|
||||
// io.start();
|
||||
//
|
||||
Handle<Value> IOWatcher::New(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
IOWatcher *s = new IOWatcher();
|
||||
s->Wrap(args.This());
|
||||
return args.This();
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> IOWatcher::Start(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
IOWatcher *io = ObjectWrap::Unwrap<IOWatcher>(args.Holder());
|
||||
io->Start();
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> IOWatcher::Stop(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
IOWatcher *io = ObjectWrap::Unwrap<IOWatcher>(args.Holder());
|
||||
io->Stop();
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
|
||||
void IOWatcher::Start() {
|
||||
if (!ev_is_active(&watcher_)) {
|
||||
ev_io_start(EV_DEFAULT_UC_ &watcher_);
|
||||
Ref();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IOWatcher::Stop() {
|
||||
if (ev_is_active(&watcher_)) {
|
||||
ev_io_stop(EV_DEFAULT_UC_ &watcher_);
|
||||
Unref();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> IOWatcher::Set(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
IOWatcher *io = ObjectWrap::Unwrap<IOWatcher>(args.Holder());
|
||||
|
||||
if (!args[0]->IsInt32()) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("First arg should be a file descriptor.")));
|
||||
}
|
||||
|
||||
int fd = args[0]->Int32Value();
|
||||
|
||||
if (!args[1]->IsBoolean()) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("Second arg should boolean (readable).")));
|
||||
}
|
||||
|
||||
int events = 0;
|
||||
|
||||
if (args[1]->IsTrue()) events |= EV_READ;
|
||||
|
||||
if (!args[2]->IsBoolean()) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("Third arg should boolean (writable).")));
|
||||
}
|
||||
|
||||
if (args[2]->IsTrue()) events |= EV_WRITE;
|
||||
|
||||
ev_io_set(&io->watcher_, fd, events);
|
||||
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace node
|
44
src/node_io_watcher.h
Normal file
44
src/node_io_watcher.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2009 Ryan Dahl <ry@tinyclouds.org>
|
||||
#ifndef NODE_IO_H_
|
||||
#define NODE_IO_H_
|
||||
|
||||
#include <node_object_wrap.h>
|
||||
#include <ev.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
class IOWatcher : ObjectWrap {
|
||||
public:
|
||||
static void Initialize(v8::Handle<v8::Object> target);
|
||||
|
||||
protected:
|
||||
static v8::Persistent<v8::FunctionTemplate> constructor_template;
|
||||
|
||||
IOWatcher() : ObjectWrap() {
|
||||
ev_init(&watcher_, IOWatcher::Callback);
|
||||
watcher_.data = this;
|
||||
}
|
||||
|
||||
~IOWatcher() {
|
||||
ev_io_stop(EV_DEFAULT_UC_ &watcher_);
|
||||
assert(!ev_is_active(&watcher_));
|
||||
assert(!ev_is_pending(&watcher_));
|
||||
}
|
||||
|
||||
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Start(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Stop(const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Set(const v8::Arguments& args);
|
||||
|
||||
private:
|
||||
static void Callback(EV_P_ ev_io *watcher, int revents);
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
ev_io watcher_;
|
||||
};
|
||||
|
||||
} // namespace node
|
||||
#endif // NODE_IO_H_
|
||||
|
1309
src/node_net2.cc
Normal file
1309
src/node_net2.cc
Normal file
File diff suppressed because it is too large
Load Diff
12
src/node_net2.h
Normal file
12
src/node_net2.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef NODE_NET2
|
||||
#define NODE_NET2
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
void InitNet2(v8::Handle<v8::Object> target);
|
||||
|
||||
}
|
||||
|
||||
#endif // NODE_NET2
|
@ -8,10 +8,8 @@
|
||||
#include <errno.h>
|
||||
|
||||
using namespace v8;
|
||||
using namespace node;
|
||||
namespace node {
|
||||
|
||||
static Persistent<Object> stdio;
|
||||
static Persistent<Function> emit;
|
||||
|
||||
static struct coupling *stdin_coupling = NULL;
|
||||
static struct coupling *stdout_coupling = NULL;
|
||||
@ -19,33 +17,8 @@ static struct coupling *stdout_coupling = NULL;
|
||||
static int stdin_fd = -1;
|
||||
static int stdout_fd = -1;
|
||||
|
||||
static evcom_reader in;
|
||||
static evcom_writer out;
|
||||
|
||||
static enum encoding stdin_encoding;
|
||||
|
||||
static void
|
||||
EmitInput (Local<Value> input)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
Local<Value> argv[2] = { String::NewSymbol("data"), input };
|
||||
|
||||
emit->Call(stdio, 2, argv);
|
||||
}
|
||||
|
||||
static void
|
||||
EmitClose (void)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
Local<Value> argv[1] = { String::NewSymbol("close") };
|
||||
|
||||
emit->Call(stdio, 1, argv);
|
||||
}
|
||||
|
||||
|
||||
static inline Local<Value> errno_exception(int errorno) {
|
||||
static Local<Value> errno_exception(int errorno) {
|
||||
Local<Value> e = Exception::Error(String::NewSymbol(strerror(errorno)));
|
||||
Local<Object> obj = e->ToObject();
|
||||
obj->Set(String::NewSymbol("errno"), Integer::New(errorno));
|
||||
@ -53,7 +26,7 @@ static inline Local<Value> errno_exception(int errorno) {
|
||||
}
|
||||
|
||||
|
||||
/* STDERR IS ALWAY SYNC */
|
||||
/* STDERR IS ALWAY SYNC ALWAYS UTF8 */
|
||||
static Handle<Value>
|
||||
WriteError (const Arguments& args)
|
||||
{
|
||||
@ -81,84 +54,8 @@ WriteError (const Arguments& args)
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
static Handle<Value>
|
||||
Write (const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
if (args.Length() == 0) {
|
||||
return ThrowException(Exception::Error(String::New("Bad argument")));
|
||||
}
|
||||
|
||||
enum encoding enc = UTF8;
|
||||
if (args.Length() > 1) enc = ParseEncoding(args[1], UTF8);
|
||||
|
||||
ssize_t len = DecodeBytes(args[0], enc);
|
||||
|
||||
if (len < 0) {
|
||||
Local<Value> exception = Exception::TypeError(String::New("Bad argument"));
|
||||
return ThrowException(exception);
|
||||
}
|
||||
|
||||
char buf[len];
|
||||
ssize_t written = DecodeWrite(buf, len, args[0], enc);
|
||||
|
||||
assert(written == len);
|
||||
|
||||
evcom_writer_write(&out, buf, len);
|
||||
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
static void
|
||||
detach_in (evcom_reader *r)
|
||||
{
|
||||
assert(r == &in);
|
||||
HandleScope scope;
|
||||
|
||||
EmitClose();
|
||||
|
||||
evcom_reader_detach(&in);
|
||||
|
||||
if (stdin_coupling) {
|
||||
coupling_destroy(stdin_coupling);
|
||||
stdin_coupling = NULL;
|
||||
}
|
||||
|
||||
stdin_fd = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
detach_out (evcom_writer* w)
|
||||
{
|
||||
assert(w == &out);
|
||||
|
||||
evcom_writer_detach(&out);
|
||||
if (stdout_coupling) {
|
||||
coupling_destroy(stdout_coupling);
|
||||
stdout_coupling = NULL;
|
||||
}
|
||||
stdout_fd = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
on_read (evcom_reader *r, const void *buf, size_t len)
|
||||
{
|
||||
assert(r == &in);
|
||||
HandleScope scope;
|
||||
|
||||
if (!len) {
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Value> data = Encode(buf, len, stdin_encoding);
|
||||
|
||||
EmitInput(data);
|
||||
}
|
||||
|
||||
static inline int
|
||||
set_nonblock (int fd)
|
||||
{
|
||||
static inline int SetNonblock(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) return -1;
|
||||
|
||||
@ -168,20 +65,14 @@ set_nonblock (int fd)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Handle<Value>
|
||||
Open (const Arguments& args)
|
||||
{
|
||||
|
||||
static Handle<Value> OpenStdin(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
|
||||
if (stdin_fd >= 0) {
|
||||
return ThrowException(Exception::Error(String::New("stdin already open")));
|
||||
}
|
||||
|
||||
stdin_encoding = UTF8;
|
||||
if (args.Length() > 0) {
|
||||
stdin_encoding = ParseEncoding(args[0]);
|
||||
}
|
||||
|
||||
if (isatty(STDIN_FILENO)) {
|
||||
// XXX selecting on tty fds wont work in windows.
|
||||
// Must ALWAYS make a coupling on shitty platforms.
|
||||
@ -190,34 +81,11 @@ Open (const Arguments& args)
|
||||
stdin_coupling = coupling_new_pull(STDIN_FILENO);
|
||||
stdin_fd = coupling_nonblocking_fd(stdin_coupling);
|
||||
}
|
||||
set_nonblock(stdin_fd);
|
||||
SetNonblock(stdin_fd);
|
||||
|
||||
evcom_reader_init(&in);
|
||||
|
||||
in.on_read = on_read;
|
||||
in.on_close = detach_in;
|
||||
|
||||
evcom_reader_set(&in, stdin_fd);
|
||||
evcom_reader_attach(EV_DEFAULT_ &in);
|
||||
|
||||
return Undefined();
|
||||
return scope.Close(Integer::New(stdin_fd));
|
||||
}
|
||||
|
||||
static Handle<Value>
|
||||
Close (const Arguments& args)
|
||||
{
|
||||
HandleScope scope;
|
||||
|
||||
assert(stdio == args.Holder());
|
||||
|
||||
if (stdin_fd < 0) {
|
||||
return ThrowException(Exception::Error(String::New("stdin not open")));
|
||||
}
|
||||
|
||||
evcom_reader_close(&in);
|
||||
|
||||
return Undefined();
|
||||
}
|
||||
|
||||
void Stdio::Flush() {
|
||||
if (stdout_fd >= 0) {
|
||||
@ -232,28 +100,10 @@ void Stdio::Flush() {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Stdio::Initialize (v8::Handle<v8::Object> target)
|
||||
{
|
||||
|
||||
void Stdio::Initialize(v8::Handle<v8::Object> target) {
|
||||
HandleScope scope;
|
||||
|
||||
Local<Object> stdio_local =
|
||||
EventEmitter::constructor_template->GetFunction()->NewInstance(0, NULL);
|
||||
|
||||
stdio = Persistent<Object>::New(stdio_local);
|
||||
|
||||
NODE_SET_METHOD(stdio, "open", Open);
|
||||
NODE_SET_METHOD(stdio, "write", Write);
|
||||
NODE_SET_METHOD(stdio, "writeError", WriteError);
|
||||
NODE_SET_METHOD(stdio, "close", Close);
|
||||
|
||||
target->Set(String::NewSymbol("stdio"), stdio);
|
||||
|
||||
Local<Value> emit_v = stdio->Get(String::NewSymbol("emit"));
|
||||
assert(emit_v->IsFunction());
|
||||
Local<Function> emit_f = Local<Function>::Cast(emit_v);
|
||||
emit = Persistent<Function>::New(emit_f);
|
||||
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
// XXX selecting on tty fds wont work in windows.
|
||||
// Must ALWAYS make a coupling on shitty platforms.
|
||||
@ -262,10 +112,13 @@ Stdio::Initialize (v8::Handle<v8::Object> target)
|
||||
stdout_coupling = coupling_new_push(STDOUT_FILENO);
|
||||
stdout_fd = coupling_nonblocking_fd(stdout_coupling);
|
||||
}
|
||||
set_nonblock(stdout_fd);
|
||||
SetNonblock(stdout_fd);
|
||||
|
||||
evcom_writer_init(&out);
|
||||
out.on_close = detach_out;
|
||||
evcom_writer_set(&out, stdout_fd);
|
||||
evcom_writer_attach(EV_DEFAULT_ &out);
|
||||
target->Set(String::NewSymbol("stdoutFD"), Integer::New(stdout_fd));
|
||||
|
||||
NODE_SET_METHOD(target, "writeError", WriteError);
|
||||
NODE_SET_METHOD(target, "openStdin", OpenStdin);
|
||||
}
|
||||
|
||||
|
||||
} // namespace node
|
||||
|
@ -2,9 +2,7 @@
|
||||
#define node_stdio_h
|
||||
|
||||
#include <node.h>
|
||||
|
||||
#include <v8.h>
|
||||
#include <evcom.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
@ -14,5 +12,5 @@ public:
|
||||
static void Flush ();
|
||||
};
|
||||
|
||||
} // namespace node
|
||||
#endif
|
||||
} // namespace node
|
||||
#endif // node_stdio_h
|
||||
|
@ -27,6 +27,7 @@ Timer::Initialize (Handle<Object> target)
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "start", Timer::Start);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", Timer::Stop);
|
||||
NODE_SET_PROTOTYPE_METHOD(constructor_template, "again", Timer::Again);
|
||||
|
||||
constructor_template->InstanceTemplate()->SetAccessor(repeat_symbol,
|
||||
RepeatGetter, RepeatSetter);
|
||||
@ -140,3 +141,30 @@ void Timer::Stop () {
|
||||
Unref();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> Timer::Again(const Arguments& args) {
|
||||
HandleScope scope;
|
||||
Timer *timer = ObjectWrap::Unwrap<Timer>(args.Holder());
|
||||
|
||||
int was_active = ev_is_active(&timer->watcher_);
|
||||
|
||||
if (args.Length() > 0) {
|
||||
ev_tstamp repeat = NODE_V8_UNIXTIME(args[0]);
|
||||
if (repeat > 0) timer->watcher_.repeat = repeat;
|
||||
}
|
||||
|
||||
ev_timer_again(EV_DEFAULT_UC_ &timer->watcher_);
|
||||
|
||||
// ev_timer_again can start or stop the watcher.
|
||||
// So we need to check what happened and adjust the ref count
|
||||
// appropriately.
|
||||
|
||||
if (ev_is_active(&timer->watcher_)) {
|
||||
if (!was_active) timer->Ref();
|
||||
} else {
|
||||
if (was_active) timer->Unref();
|
||||
}
|
||||
|
||||
return Undefined();
|
||||
}
|
||||
|
@ -15,12 +15,16 @@ class Timer : ObjectWrap {
|
||||
protected:
|
||||
static v8::Persistent<v8::FunctionTemplate> constructor_template;
|
||||
|
||||
Timer () : ObjectWrap () { }
|
||||
Timer () : ObjectWrap () {
|
||||
// dummy timeout values
|
||||
ev_timer_init(&watcher_, OnTimeout, 0., 1.);
|
||||
}
|
||||
~Timer();
|
||||
|
||||
static v8::Handle<v8::Value> New (const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Start (const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Stop (const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> Again (const v8::Arguments& args);
|
||||
static v8::Handle<v8::Value> RepeatGetter (v8::Local<v8::String> property, const v8::AccessorInfo& info);
|
||||
static void RepeatSetter (v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::AccessorInfo& info);
|
||||
|
||||
|
58
test-net-server.js
Normal file
58
test-net-server.js
Normal file
@ -0,0 +1,58 @@
|
||||
process.Buffer.prototype.toString = function () {
|
||||
return this.utf8Slice(0, this.length);
|
||||
};
|
||||
|
||||
var sys = require("sys");
|
||||
var net = require("./lib/net");
|
||||
|
||||
var server = new net.Server(function (socket) {
|
||||
sys.puts("connection (" + socket.fd + "): "
|
||||
+ socket.remoteAddress
|
||||
+ " port "
|
||||
+ socket.remotePort
|
||||
);
|
||||
sys.puts("server fd: " + server.fd);
|
||||
|
||||
socket.addListener("data", function (b) {
|
||||
socket.send("pong ascii\r\n", "ascii");
|
||||
socket.send(b);
|
||||
socket.send("pong utf8\r\n", "utf8");
|
||||
if (/^quit/.test(b)) {
|
||||
socket.close();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("eof", function () {
|
||||
sys.puts("server peer eof");
|
||||
socket.close();
|
||||
});
|
||||
|
||||
socket.addListener('drain', function () {
|
||||
sys.puts("server-side socket drain");
|
||||
});
|
||||
});
|
||||
|
||||
server.addListener("listening", function () {
|
||||
var c = net.createConnection("/tmp/node.sock");
|
||||
c.addListener('connect', function () {
|
||||
sys.puts("!!!client connected");
|
||||
c.send("hello\n");
|
||||
});
|
||||
|
||||
c.addListener('drain', function () {
|
||||
sys.puts("!!!client drain");
|
||||
});
|
||||
|
||||
c.addListener('data', function (d) {
|
||||
sys.puts("!!!client got: " + JSON.stringify(d.toString()));
|
||||
c.close();
|
||||
});
|
||||
|
||||
c.addListener('eof', function (d) {
|
||||
sys.puts("!!!client eof");
|
||||
});
|
||||
});
|
||||
|
||||
server.listen("/tmp/node.sock");
|
||||
sys.puts("server fd: " + server.fd);
|
67
test/disabled/test-net-fd-passing.js
Normal file
67
test/disabled/test-net-fd-passing.js
Normal file
@ -0,0 +1,67 @@
|
||||
process.mixin(require("../common"));
|
||||
net = require("net");
|
||||
|
||||
var tests_run = 0;
|
||||
|
||||
function fdPassingTest(path, port) {
|
||||
var greeting = "howdy";
|
||||
var message = "beep toot";
|
||||
var expectedData = ["[greeting] " + greeting, "[echo] " + message];
|
||||
|
||||
var receiverArgs = [fixturesDir + "/net-fd-passing-receiver.js", path, greeting];
|
||||
var receiver = process.createChildProcess(process.ARGV[0], receiverArgs);
|
||||
|
||||
var initializeSender = function() {
|
||||
var fdHighway = new net.Socket();
|
||||
|
||||
fdHighway.addListener("connect", function() {
|
||||
var sender = net.createServer(function(socket) {
|
||||
fdHighway.sendFD(socket);
|
||||
socket.flush();
|
||||
socket.forceClose(); // want to close() the fd, not shutdown()
|
||||
});
|
||||
|
||||
sender.addListener("listening", function() {
|
||||
var client = net.createConnection(port);
|
||||
|
||||
client.addListener("connect", function() {
|
||||
client.write(message);
|
||||
});
|
||||
|
||||
client.addListener("data", function(data) {
|
||||
assert.equal(expectedData[0], data);
|
||||
if (expectedData.length > 1) {
|
||||
expectedData.shift();
|
||||
}
|
||||
else {
|
||||
// time to shut down
|
||||
fdHighway.close();
|
||||
sender.close();
|
||||
client.forceClose();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tests_run += 1;
|
||||
sender.listen(port);
|
||||
});
|
||||
|
||||
fdHighway.connect(path);
|
||||
|
||||
|
||||
};
|
||||
|
||||
receiver.addListener("output", function(data) {
|
||||
var initialized = false;
|
||||
if ((! initialized) && (data == "ready")) {
|
||||
initializeSender();
|
||||
initialized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fdPassingTest("/tmp/passing-socket-test", 31075);
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(1, tests_run);
|
||||
});
|
11
test/fixtures/echo.js
vendored
11
test/fixtures/echo.js
vendored
@ -1,12 +1,13 @@
|
||||
require("../common");
|
||||
process.stdio.open();
|
||||
|
||||
print("hello world\r\n");
|
||||
|
||||
process.stdio.addListener("data", function (data) {
|
||||
print(data);
|
||||
var stdin = process.openStdin();
|
||||
|
||||
stdin.addListener("data", function (data) {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
process.stdio.addListener("close", function () {
|
||||
process.stdio.close();
|
||||
stdin.addListener("end", function () {
|
||||
process.stdout.close();
|
||||
});
|
||||
|
32
test/fixtures/net-fd-passing-receiver.js
vendored
Normal file
32
test/fixtures/net-fd-passing-receiver.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
process.mixin(require("../common"));
|
||||
net = require("net");
|
||||
|
||||
path = process.ARGV[2];
|
||||
greeting = process.ARGV[3];
|
||||
|
||||
receiver = net.createServer(function(socket) {
|
||||
socket.addListener("fd", function(fd) {
|
||||
var peerInfo = process.getpeername(fd);
|
||||
peerInfo.fd = fd;
|
||||
var passedSocket = new net.Socket(peerInfo);
|
||||
|
||||
passedSocket.addListener("eof", function() {
|
||||
passedSocket.close();
|
||||
});
|
||||
|
||||
passedSocket.addListener("data", function(data) {
|
||||
passedSocket.send("[echo] " + data);
|
||||
});
|
||||
passedSocket.addListener("close", function() {
|
||||
receiver.close();
|
||||
});
|
||||
passedSocket.send("[greeting] " + greeting);
|
||||
});
|
||||
});
|
||||
|
||||
/* To signal the test runne we're up and listening */
|
||||
receiver.addListener("listening", function() {
|
||||
print("ready");
|
||||
});
|
||||
|
||||
receiver.listen(path);
|
4
test/fixtures/print-chars.js
vendored
4
test/fixtures/print-chars.js
vendored
@ -3,8 +3,8 @@ require("../common");
|
||||
var n = parseInt(process.argv[2]);
|
||||
|
||||
var s = "";
|
||||
for (var i = 0; i < n-1; i++) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
s += 'c';
|
||||
}
|
||||
|
||||
puts(s); // \n is the nth char.
|
||||
process.stdout.write(s);
|
||||
|
@ -1,33 +0,0 @@
|
||||
require("../common");
|
||||
|
||||
var N = 40;
|
||||
var finished = false;
|
||||
|
||||
function spawn (i) {
|
||||
var child = process.createChildProcess( 'python'
|
||||
, ['-c', 'print 500 * 1024 * "C"']
|
||||
);
|
||||
var output = "";
|
||||
|
||||
child.addListener("output", function(chunk) {
|
||||
if (chunk) output += chunk;
|
||||
});
|
||||
|
||||
child.addListener("error", function(chunk) {
|
||||
if (chunk) error(chunk)
|
||||
});
|
||||
|
||||
child.addListener("exit", function () {
|
||||
puts(output);
|
||||
if (i < N)
|
||||
spawn(i+1);
|
||||
else
|
||||
finished = true;
|
||||
});
|
||||
}
|
||||
|
||||
spawn(0);
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(true, finished);
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
// settings
|
||||
var bytes = 1024*40;
|
||||
var concurrency = 100;
|
||||
@ -13,7 +13,7 @@ for (var i = 0; i < bytes; i++) {
|
||||
body += "C";
|
||||
}
|
||||
|
||||
var server = tcp.createServer(function (c) {
|
||||
var server = net.createServer(function (c) {
|
||||
c.addListener("connect", function () {
|
||||
total_connections++;
|
||||
print("#");
|
||||
@ -24,8 +24,10 @@ var server = tcp.createServer(function (c) {
|
||||
server.listen(PORT);
|
||||
|
||||
function runClient (callback) {
|
||||
var client = tcp.createConnection(PORT);
|
||||
var client = net.createConnection(PORT);
|
||||
|
||||
client.connections = 0;
|
||||
|
||||
client.setEncoding("utf8");
|
||||
|
||||
client.addListener("connect", function () {
|
||||
@ -38,14 +40,25 @@ function runClient (callback) {
|
||||
this.recved += chunk;
|
||||
});
|
||||
|
||||
client.addListener("end", function (had_error) {
|
||||
client.addListener("end", function () {
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.addListener("error", function (e) {
|
||||
puts("\n\nERROOOOOr");
|
||||
throw e;
|
||||
});
|
||||
|
||||
client.addListener("close", function (had_error) {
|
||||
print(".");
|
||||
assert.equal(false, had_error);
|
||||
assert.equal(bytes, client.recved.length);
|
||||
|
||||
if (client.fd) {
|
||||
puts(client.fd);
|
||||
}
|
||||
assert.ok(!client.fd);
|
||||
|
||||
if (this.connections < connections_per_client) {
|
||||
this.connect(PORT);
|
||||
} else {
|
||||
@ -54,13 +67,14 @@ function runClient (callback) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var finished_clients = 0;
|
||||
for (var i = 0; i < concurrency; i++) {
|
||||
runClient(function () {
|
||||
if (++finished_clients == concurrency) server.close();
|
||||
});
|
||||
}
|
||||
server.addListener('listening', function () {
|
||||
var finished_clients = 0;
|
||||
for (var i = 0; i < concurrency; i++) {
|
||||
runClient(function () {
|
||||
if (++finished_clients == concurrency) server.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(connections_per_client * concurrency, total_connections);
|
||||
|
@ -1,8 +1,8 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
N = 200;
|
||||
|
||||
server = tcp.createServer(function (connection) {
|
||||
server = net.createServer(function (connection) {
|
||||
function write (j) {
|
||||
if (j >= N) {
|
||||
connection.close();
|
||||
@ -21,7 +21,7 @@ server.listen(PORT);
|
||||
recv = "";
|
||||
chars_recved = 0;
|
||||
|
||||
client = tcp.createConnection(PORT);
|
||||
client = net.createConnection(PORT);
|
||||
client.setEncoding("ascii");
|
||||
client.addListener("data", function (d) {
|
||||
print(d);
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
|
||||
|
||||
var tests_run = 0;
|
||||
@ -10,7 +10,7 @@ function pingPongTest (port, host, on_complete) {
|
||||
var count = 0;
|
||||
var client_closed = false;
|
||||
|
||||
var server = tcp.createServer(function (socket) {
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.setEncoding("utf8");
|
||||
|
||||
socket.addListener("data", function (data) {
|
||||
@ -44,7 +44,7 @@ function pingPongTest (port, host, on_complete) {
|
||||
});
|
||||
server.listen(port, host);
|
||||
|
||||
var client = tcp.createConnection(port, host);
|
||||
var client = net.createConnection(port, host);
|
||||
|
||||
client.setEncoding("utf8");
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
|
||||
var tests_run = 0;
|
||||
|
||||
@ -8,13 +8,13 @@ function pingPongTest (port, host, on_complete) {
|
||||
var count = 0;
|
||||
var sent_final_ping = false;
|
||||
|
||||
var server = tcp.createServer(function (socket) {
|
||||
var server = net.createServer(function (socket) {
|
||||
assert.equal(true, socket.remoteAddress !== null);
|
||||
assert.equal(true, socket.remoteAddress !== undefined);
|
||||
if (host === "127.0.0.1")
|
||||
assert.equal(socket.remoteAddress, "127.0.0.1");
|
||||
else if (host == null)
|
||||
assert.equal(socket.remoteAddress, "127.0.0.1");
|
||||
assert.equal(socket.remoteAddress, "::1");
|
||||
|
||||
socket.setEncoding("utf8");
|
||||
socket.setNoDelay();
|
||||
@ -42,7 +42,7 @@ function pingPongTest (port, host, on_complete) {
|
||||
});
|
||||
server.listen(port, host);
|
||||
|
||||
var client = tcp.createConnection(port, host);
|
||||
var client = net.createConnection(port, host);
|
||||
|
||||
client.setEncoding("utf8");
|
||||
|
||||
@ -52,6 +52,8 @@ function pingPongTest (port, host, on_complete) {
|
||||
});
|
||||
|
||||
client.addListener("data", function (data) {
|
||||
puts('client got: ' + data);
|
||||
|
||||
assert.equal("PONG", data);
|
||||
count += 1;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
N = 60*1024; // 30kb
|
||||
net = require("net");
|
||||
N = 160*1024; // 30kb
|
||||
|
||||
puts("build big string");
|
||||
var body = "";
|
||||
@ -10,7 +10,7 @@ for (var i = 0; i < N; i++) {
|
||||
|
||||
puts("start server on port " + PORT);
|
||||
|
||||
server = tcp.createServer(function (connection) {
|
||||
server = net.createServer(function (connection) {
|
||||
connection.addListener("connect", function () {
|
||||
assert.equal(false, connection.write(body));
|
||||
connection.close();
|
||||
@ -24,7 +24,7 @@ npauses = 0;
|
||||
|
||||
|
||||
var paused = false;
|
||||
client = tcp.createConnection(PORT);
|
||||
client = net.createConnection(PORT);
|
||||
client.setEncoding("ascii");
|
||||
client.addListener("data", function (d) {
|
||||
chars_recved += d.length;
|
||||
|
76
test/simple/test-buffer.js
Normal file
76
test/simple/test-buffer.js
Normal file
@ -0,0 +1,76 @@
|
||||
require("../common");
|
||||
assert = require("assert");
|
||||
|
||||
var Buffer = require('buffer').Buffer;
|
||||
|
||||
var b = new Buffer(1024);
|
||||
|
||||
puts("b.length == " + b.length);
|
||||
assert.equal(1024, b.length);
|
||||
|
||||
for (var i = 0; i < 1024; i++) {
|
||||
assert.ok(b[i] >= 0);
|
||||
b[i] = i % 256;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 1024; i++) {
|
||||
assert.equal(i % 256, b[i]);
|
||||
}
|
||||
|
||||
var asciiString = "hello world";
|
||||
var offset = 100;
|
||||
for (var j = 0; j < 500; j++) {
|
||||
|
||||
for (var i = 0; i < asciiString.length; i++) {
|
||||
b[i] = asciiString.charCodeAt(i);
|
||||
}
|
||||
var asciiSlice = b.asciiSlice(0, asciiString.length);
|
||||
assert.equal(asciiString, asciiSlice);
|
||||
|
||||
var written = b.asciiWrite(asciiString, offset);
|
||||
assert.equal(asciiString.length, written);
|
||||
var asciiSlice = b.asciiSlice(offset, offset+asciiString.length);
|
||||
assert.equal(asciiString, asciiSlice);
|
||||
|
||||
var sliceA = b.slice(offset, offset+asciiString.length);
|
||||
var sliceB = b.slice(offset, offset+asciiString.length);
|
||||
for (var i = 0; i < asciiString.length; i++) {
|
||||
assert.equal(sliceA[i], sliceB[i]);
|
||||
}
|
||||
|
||||
// TODO utf8 slice tests
|
||||
}
|
||||
|
||||
|
||||
for (var j = 0; j < 100; j++) {
|
||||
var slice = b.slice(100, 150);
|
||||
assert.equal(50, slice.length);
|
||||
for (var i = 0; i < 50; i++) {
|
||||
assert.equal(b[100+i], slice[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// unpack
|
||||
|
||||
var b = new Buffer(10);
|
||||
b[0] = 0x00;
|
||||
b[1] = 0x01;
|
||||
b[2] = 0x03;
|
||||
b[3] = 0x00;
|
||||
|
||||
assert.deepEqual([0x0001], b.unpack('n', 0));
|
||||
assert.deepEqual([0x0001, 0x0300], b.unpack('nn', 0));
|
||||
assert.deepEqual([0x0103], b.unpack('n', 1));
|
||||
assert.deepEqual([0x0300], b.unpack('n', 2));
|
||||
assert.deepEqual([0x00010300], b.unpack('N', 0));
|
||||
assert.throws(function () {
|
||||
b.unpack('N', 8);
|
||||
});
|
||||
|
||||
b[4] = 0xDE;
|
||||
b[5] = 0xAD;
|
||||
b[6] = 0xBE;
|
||||
b[7] = 0xEF;
|
||||
|
||||
assert.deepEqual([0xDEADBEEF], b.unpack('N', 4));
|
@ -1,14 +1,19 @@
|
||||
require("../common");
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var pwd_called = false;
|
||||
|
||||
function pwd (callback) {
|
||||
var output = "";
|
||||
var child = process.createChildProcess("pwd");
|
||||
child.addListener("output", function (s) {
|
||||
var child = spawn("pwd");
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.addListener("data", function (s) {
|
||||
puts("stdout: " + JSON.stringify(s));
|
||||
if (s) output += s;
|
||||
output += s;
|
||||
});
|
||||
|
||||
child.addListener("exit", function (c) {
|
||||
puts("exit: " + c);
|
||||
assert.equal(0, c);
|
@ -1,10 +1,15 @@
|
||||
require("../common");
|
||||
child = process.createChildProcess('/usr/bin/env', [], {'HELLO' : 'WORLD'});
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
child = spawn('/usr/bin/env', [], {'HELLO' : 'WORLD'});
|
||||
|
||||
response = "";
|
||||
|
||||
child.addListener("output", function (chunk) {
|
||||
puts("stdout: " + JSON.stringify(chunk));
|
||||
if (chunk) response += chunk;
|
||||
child.stdout.setEncoding('utf8');
|
||||
|
||||
child.stdout.addListener("data", function (chunk) {
|
||||
puts("stdout: " + chunk);
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
process.addListener('exit', function () {
|
||||
|
41
test/simple/test-child-process-ipc.js
Normal file
41
test/simple/test-child-process-ipc.js
Normal file
@ -0,0 +1,41 @@
|
||||
require("../common");
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var sub = path.join(fixturesDir, 'echo.js');
|
||||
|
||||
var gotHelloWorld = false;
|
||||
var gotEcho = false;
|
||||
|
||||
var child = spawn(process.argv[0], [sub]);
|
||||
|
||||
child.stderr.addListener("data", function (data){
|
||||
puts("parent stderr: " + data);
|
||||
});
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
|
||||
child.stdout.addListener("data", function (data){
|
||||
puts('child said: ' + JSON.stringify(data));
|
||||
if (!gotHelloWorld) {
|
||||
assert.equal("hello world\r\n", data);
|
||||
gotHelloWorld = true;
|
||||
child.stdin.write('echo me\r\n');
|
||||
} else {
|
||||
assert.equal("echo me\r\n", data);
|
||||
gotEcho = true;
|
||||
child.stdin.close();
|
||||
}
|
||||
});
|
||||
|
||||
child.stdout.addListener("end", function (data){
|
||||
puts('child end');
|
||||
});
|
||||
|
||||
|
||||
process.addListener('exit', function () {
|
||||
assert.ok(gotHelloWorld);
|
||||
assert.ok(gotEcho);
|
||||
});
|
38
test/simple/test-child-process-kill.js
Normal file
38
test/simple/test-child-process-kill.js
Normal file
@ -0,0 +1,38 @@
|
||||
require("../common");
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var exitStatus = -1;
|
||||
var gotStdoutEOF = false;
|
||||
var gotStderrEOF = false;
|
||||
|
||||
var cat = spawn("cat");
|
||||
|
||||
|
||||
cat.stdout.addListener("data", function (chunk) {
|
||||
assert.ok(false);
|
||||
});
|
||||
|
||||
cat.stdout.addListener("end", function () {
|
||||
gotStdoutEOF = true;
|
||||
});
|
||||
|
||||
cat.stderr.addListener("data", function (chunk) {
|
||||
assert.ok(false);
|
||||
});
|
||||
|
||||
cat.stderr.addListener("end", function () {
|
||||
gotStderrEOF = true;
|
||||
});
|
||||
|
||||
cat.addListener("exit", function (status) {
|
||||
exitStatus = status;
|
||||
});
|
||||
|
||||
cat.kill();
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.ok(exitStatus > 0);
|
||||
assert.ok(gotStdoutEOF);
|
||||
assert.ok(gotStderrEOF);
|
||||
});
|
36
test/simple/test-child-process-spawn-loop.js
Normal file
36
test/simple/test-child-process-spawn-loop.js
Normal file
@ -0,0 +1,36 @@
|
||||
require("../common");
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var SIZE = 1000 * 1024;
|
||||
var N = 40;
|
||||
var finished = false;
|
||||
|
||||
function doSpawn (i) {
|
||||
var child = spawn( 'python', ['-c', 'print ' + SIZE + ' * "C"']);
|
||||
var count = 0;
|
||||
|
||||
child.stdout.setEncoding('ascii');
|
||||
child.stdout.addListener("data", function (chunk) {
|
||||
count += chunk.length;
|
||||
});
|
||||
|
||||
child.stderr.addListener("data", function (chunk) {
|
||||
puts('stderr: ' + chunk);
|
||||
});
|
||||
|
||||
child.addListener("exit", function () {
|
||||
assert.equal(SIZE + 1, count); // + 1 for \n
|
||||
if (i < N) {
|
||||
doSpawn(i+1);
|
||||
} else {
|
||||
finished = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
doSpawn(0);
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.ok(finished);
|
||||
});
|
48
test/simple/test-child-process-stdin.js
Normal file
48
test/simple/test-child-process-stdin.js
Normal file
@ -0,0 +1,48 @@
|
||||
require("../common");
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
var cat = spawn("cat");
|
||||
cat.stdin.write("hello");
|
||||
cat.stdin.write(" ");
|
||||
cat.stdin.write("world");
|
||||
cat.stdin.close();
|
||||
|
||||
var response = "";
|
||||
var exitStatus = -1;
|
||||
|
||||
var gotStdoutEOF = false;
|
||||
|
||||
cat.stdout.setEncoding('utf8');
|
||||
cat.stdout.addListener("data", function (chunk) {
|
||||
puts("stdout: " + chunk);
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
cat.stdout.addListener('end', function () {
|
||||
gotStdoutEOF = true;
|
||||
});
|
||||
|
||||
|
||||
var gotStderrEOF = false;
|
||||
|
||||
cat.stderr.addListener("data", function (chunk) {
|
||||
// shouldn't get any stderr output
|
||||
assert.ok(false);
|
||||
});
|
||||
|
||||
cat.stderr.addListener("end", function (chunk) {
|
||||
gotStderrEOF = true;
|
||||
});
|
||||
|
||||
|
||||
cat.addListener("exit", function (status) {
|
||||
puts("exit event");
|
||||
exitStatus = status;
|
||||
assert.equal("hello world", response);
|
||||
});
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(0, exitStatus);
|
||||
assert.equal("hello world", response);
|
||||
});
|
27
test/simple/test-child-process-stdout-flush.js
Normal file
27
test/simple/test-child-process-stdout-flush.js
Normal file
@ -0,0 +1,27 @@
|
||||
require("../common");
|
||||
var path = require('path');
|
||||
var spawn = require('child_process').spawn;
|
||||
var sub = path.join(fixturesDir, 'print-chars.js');
|
||||
|
||||
n = 500000;
|
||||
|
||||
var child = spawn(process.argv[0], [sub, n]);
|
||||
|
||||
var count = 0;
|
||||
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stderr.addListener("data", function (data) {
|
||||
puts("parent stderr: " + data);
|
||||
assert.ok(false);
|
||||
});
|
||||
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stdout.addListener("data", function (data) {
|
||||
count += data.length;
|
||||
puts(count);
|
||||
});
|
||||
|
||||
child.addListener("exit", function (data) {
|
||||
assert.equal(n, count);
|
||||
puts("okay");
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
success_count = 0;
|
||||
error_count = 0;
|
||||
|
||||
|
@ -60,4 +60,4 @@ process.addListener('exit', function() {
|
||||
for (var k in callbacks) {
|
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -59,4 +59,4 @@ process.addListener('exit', function() {
|
||||
for (var k in callbacks) {
|
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
http = require("http");
|
||||
|
||||
var body = "hello world\n";
|
||||
@ -13,7 +13,7 @@ var server = http.createServer(function (req, res) {
|
||||
})
|
||||
server.listen(PORT);
|
||||
|
||||
var c = tcp.createConnection(PORT);
|
||||
var c = net.createConnection(PORT);
|
||||
|
||||
c.setEncoding("utf8");
|
||||
|
||||
|
@ -10,7 +10,7 @@ var server = http.createServer(function(req, res) {
|
||||
});
|
||||
server.listen(PORT);
|
||||
|
||||
http.cat("http://localhost:"+PORT+"/", "utf8", function (err, data) {
|
||||
http.cat("http://127.0.0.1:"+PORT+"/", "utf8", function (err, data) {
|
||||
if (err) throw err;
|
||||
assert.equal(UTF8_STRING, data);
|
||||
server.close();
|
||||
|
@ -33,7 +33,7 @@ req.write('3\n');
|
||||
|
||||
puts("client finished sending request");
|
||||
req.addListener('response', function(res) {
|
||||
res.setBodyEncoding("utf8");
|
||||
res.setEncoding("utf8");
|
||||
res.addListener('data', function(chunk) {
|
||||
puts(chunk);
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
http = require("http");
|
||||
|
||||
// This is a regression test for http://github.com/ry/node/issues/#issue/44
|
||||
@ -9,8 +9,8 @@ http = require("http");
|
||||
server = http.createServer(function (req, res) {});
|
||||
server.listen(PORT);
|
||||
|
||||
tcp.createConnection(PORT).addListener("connect", function () {
|
||||
this.close();
|
||||
net.createConnection(PORT).addListener("connect", function () {
|
||||
this.close();
|
||||
}).addListener("close", function () {
|
||||
server.close();
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
http = require("http");
|
||||
url = require("url");
|
||||
|
||||
@ -20,7 +20,7 @@ var s = http.createServer(function (req, res) {
|
||||
});
|
||||
s.listen(PORT);
|
||||
|
||||
var c = tcp.createConnection(PORT);
|
||||
var c = net.createConnection(PORT);
|
||||
c.addListener("connect", function () {
|
||||
c.write("GET /hello?foo=%99bar HTTP/1.1\r\n\r\n");
|
||||
c.close();
|
||||
|
62
test/simple/test-http-parser.js
Normal file
62
test/simple/test-http-parser.js
Normal file
@ -0,0 +1,62 @@
|
||||
require("../common");
|
||||
|
||||
// The purpose of this test is not to check HTTP compliance but to test the
|
||||
// binding. Tests for pathological http messages should be submitted
|
||||
// upstream to http://github.com/ry/http-parser for inclusion into
|
||||
// deps/http-parser/test.c
|
||||
|
||||
var HTTPParser = process.binding('http_parser').HTTPParser;
|
||||
|
||||
var parser = new HTTPParser("request");
|
||||
|
||||
var Buffer = require('buffer').Buffer;
|
||||
var buffer = new Buffer(1024);
|
||||
|
||||
var request = "GET /hello HTTP/1.1\r\n\r\n";
|
||||
|
||||
buffer.asciiWrite(request, 0, request.length);
|
||||
|
||||
var callbacks = 0;
|
||||
|
||||
parser.onMessageBegin = function () {
|
||||
puts("message begin");
|
||||
callbacks++;
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = function (info) {
|
||||
puts("headers complete: " + JSON.stringify(info));
|
||||
assert.equal('GET', info.method);
|
||||
assert.equal(1, info.versionMajor);
|
||||
assert.equal(1, info.versionMinor);
|
||||
callbacks++;
|
||||
};
|
||||
|
||||
parser.onURL = function (b, off, len) {
|
||||
//throw new Error("hello world");
|
||||
callbacks++;
|
||||
};
|
||||
|
||||
parser.onPath = function (b, off, length) {
|
||||
puts("path [" + off + ", " + length + "]");
|
||||
var path = b.asciiSlice(off, off+length);
|
||||
puts("path = '" + path + "'");
|
||||
assert.equal('/hello', path);
|
||||
callbacks++;
|
||||
};
|
||||
|
||||
parser.execute(buffer, 0, request.length);
|
||||
assert.equal(4, callbacks);
|
||||
|
||||
//
|
||||
// Check that if we throw an error in the callbacks that error will be
|
||||
// thrown from parser.execute()
|
||||
//
|
||||
|
||||
parser.onURL = function (b, off, len) {
|
||||
throw new Error("hello world");
|
||||
};
|
||||
|
||||
assert.throws(function () {
|
||||
parser.execute(buffer, 0, request.length);
|
||||
}, Error, "hello world");
|
||||
|
@ -6,12 +6,12 @@ var PROXY_PORT = PORT;
|
||||
var BACKEND_PORT = PORT+1;
|
||||
|
||||
var backend = http.createServer(function (req, res) {
|
||||
// debug("backend");
|
||||
debug("backend request");
|
||||
res.writeHead(200, {"content-type": "text/plain"});
|
||||
res.write("hello world\n");
|
||||
res.close();
|
||||
});
|
||||
// debug("listen backend")
|
||||
debug("listen backend")
|
||||
backend.listen(BACKEND_PORT);
|
||||
|
||||
var proxy_client = http.createClient(BACKEND_PORT);
|
||||
@ -25,31 +25,40 @@ var proxy = http.createServer(function (req, res) {
|
||||
});
|
||||
proxy_res.addListener("end", function() {
|
||||
res.close();
|
||||
// debug("proxy res");
|
||||
debug("proxy res");
|
||||
});
|
||||
});
|
||||
proxy_req.close();
|
||||
});
|
||||
// debug("listen proxy")
|
||||
debug("listen proxy")
|
||||
proxy.listen(PROXY_PORT);
|
||||
|
||||
var body = "";
|
||||
|
||||
var client = http.createClient(PROXY_PORT);
|
||||
var req = client.request("/test");
|
||||
// debug("client req")
|
||||
req.addListener('response', function (res) {
|
||||
// debug("got res");
|
||||
assert.equal(200, res.statusCode);
|
||||
res.setBodyEncoding("utf8");
|
||||
res.addListener('data', function (chunk) { body += chunk; });
|
||||
res.addListener('end', function () {
|
||||
proxy.close();
|
||||
backend.close();
|
||||
// debug("closed both");
|
||||
nlistening = 0;
|
||||
function startReq () {
|
||||
nlistening++;
|
||||
if (nlistening < 2) return;
|
||||
|
||||
var client = http.createClient(PROXY_PORT);
|
||||
var req = client.request("/test");
|
||||
debug("client req")
|
||||
req.addListener('response', function (res) {
|
||||
debug("got res");
|
||||
assert.equal(200, res.statusCode);
|
||||
res.setBodyEncoding("utf8");
|
||||
res.addListener('data', function (chunk) { body += chunk; });
|
||||
res.addListener('end', function () {
|
||||
proxy.close();
|
||||
backend.close();
|
||||
debug("closed both");
|
||||
});
|
||||
});
|
||||
});
|
||||
req.close();
|
||||
req.close();
|
||||
}
|
||||
|
||||
proxy.addListener('listening', startReq);
|
||||
backend.addListener('listening', startReq);
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(body, "hello world\n");
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
http = require("http");
|
||||
url = require("url");
|
||||
qs = require("querystring");
|
||||
@ -43,7 +43,7 @@ http.createServer(function (req, res) {
|
||||
|
||||
}).listen(PORT);
|
||||
|
||||
var c = tcp.createConnection(PORT);
|
||||
var c = net.createConnection(PORT);
|
||||
|
||||
c.setEncoding("utf8");
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require("net");
|
||||
http = require("http");
|
||||
|
||||
// wget sends an HTTP/1.0 request with Connection: Keep-Alive
|
||||
@ -29,7 +29,7 @@ var server = http.createServer(function (req, res) {
|
||||
})
|
||||
server.listen(PORT);
|
||||
|
||||
var c = tcp.createConnection(PORT);
|
||||
var c = net.createConnection(PORT);
|
||||
|
||||
c.setEncoding("utf8");
|
||||
|
||||
|
108
test/simple/test-net-pingpong.js
Normal file
108
test/simple/test-net-pingpong.js
Normal file
@ -0,0 +1,108 @@
|
||||
require("../common");
|
||||
|
||||
net = require("net");
|
||||
|
||||
var tests_run = 0;
|
||||
|
||||
function pingPongTest (port, host) {
|
||||
var N = 1000;
|
||||
var count = 0;
|
||||
var sent_final_ping = false;
|
||||
|
||||
var server = net.createServer(function (socket) {
|
||||
puts("connection: " + socket.remoteAddress);
|
||||
assert.equal(server, socket.server);
|
||||
|
||||
socket.setNoDelay();
|
||||
socket.timeout = 0;
|
||||
|
||||
socket.setEncoding('utf8');
|
||||
socket.addListener("data", function (data) {
|
||||
puts("server got: " + data);
|
||||
assert.equal(true, socket.writable);
|
||||
assert.equal(true, socket.readable);
|
||||
assert.equal(true, count <= N);
|
||||
if (/PING/.exec(data)) {
|
||||
socket.write("PONG");
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("end", function () {
|
||||
assert.equal(true, socket.writable);
|
||||
assert.equal(false, socket.readable);
|
||||
socket.close();
|
||||
});
|
||||
|
||||
socket.addListener("error", function (e) {
|
||||
throw e;
|
||||
});
|
||||
|
||||
socket.addListener("close", function () {
|
||||
puts('server socket closed');
|
||||
assert.equal(false, socket.writable);
|
||||
assert.equal(false, socket.readable);
|
||||
socket.server.close();
|
||||
});
|
||||
});
|
||||
|
||||
server.addListener("listening", function () {
|
||||
puts("server listening on " + port + " " + host);
|
||||
|
||||
var client = net.createConnection(port, host);
|
||||
|
||||
client.setEncoding('ascii');
|
||||
client.addListener("connect", function () {
|
||||
assert.equal(true, client.readable);
|
||||
assert.equal(true, client.writable);
|
||||
client.write("PING");
|
||||
});
|
||||
|
||||
client.addListener("data", function (data) {
|
||||
puts("client got: " + data);
|
||||
|
||||
assert.equal("PONG", data);
|
||||
count += 1;
|
||||
|
||||
if (sent_final_ping) {
|
||||
assert.equal(false, client.writable);
|
||||
assert.equal(true, client.readable);
|
||||
return;
|
||||
} else {
|
||||
assert.equal(true, client.writable);
|
||||
assert.equal(true, client.readable);
|
||||
}
|
||||
|
||||
if (count < N) {
|
||||
client.write("PING");
|
||||
} else {
|
||||
sent_final_ping = true;
|
||||
client.write("PING");
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
|
||||
client.addListener("close", function () {
|
||||
puts('client closed');
|
||||
assert.equal(N+1, count);
|
||||
assert.equal(true, sent_final_ping);
|
||||
tests_run += 1;
|
||||
});
|
||||
|
||||
client.addListener("error", function (e) {
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, host);
|
||||
}
|
||||
|
||||
/* All are run at once, so run on different ports */
|
||||
pingPongTest(20989, "localhost");
|
||||
pingPongTest(20988);
|
||||
pingPongTest(20997, "::1");
|
||||
pingPongTest("/tmp/pingpong.sock");
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(4, tests_run);
|
||||
puts('done');
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
require("../common");
|
||||
|
||||
var exit_status = -1;
|
||||
|
||||
var cat = process.createChildProcess("cat");
|
||||
|
||||
cat.addListener("output", function (chunk) { assert.equal(null, chunk); });
|
||||
cat.addListener("error", function (chunk) { assert.equal(null, chunk); });
|
||||
cat.addListener("exit", function (status) { exit_status = status; });
|
||||
|
||||
cat.kill();
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(true, exit_status > 0);
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
require("../common");
|
||||
|
||||
var cat = process.createChildProcess("cat");
|
||||
|
||||
var response = "";
|
||||
var exit_status = -1;
|
||||
|
||||
cat.addListener("output", function (chunk) {
|
||||
puts("stdout: " + JSON.stringify(chunk));
|
||||
if (chunk) {
|
||||
response += chunk;
|
||||
if (response === "hello world") {
|
||||
puts("closing cat");
|
||||
cat.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
cat.addListener("error", function (chunk) {
|
||||
puts("stderr: " + JSON.stringify(chunk));
|
||||
assert.equal(null, chunk);
|
||||
});
|
||||
cat.addListener("exit", function (status) {
|
||||
puts("exit event");
|
||||
exit_status = status;
|
||||
});
|
||||
|
||||
cat.write("hello");
|
||||
cat.write(" ");
|
||||
cat.write("world");
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(0, exit_status);
|
||||
assert.equal("hello world", response);
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
require("../common");
|
||||
var path = require('path');
|
||||
|
||||
var sub = path.join(fixturesDir, 'echo.js');
|
||||
|
||||
var gotHelloWorld = false;
|
||||
var gotEcho = false;
|
||||
|
||||
var child = process.createChildProcess(process.argv[0], [sub]);
|
||||
|
||||
child.addListener("error", function (data){
|
||||
puts("parent stderr: " + data);
|
||||
});
|
||||
|
||||
child.addListener("output", function (data){
|
||||
if (data) {
|
||||
puts('child said: ' + JSON.stringify(data));
|
||||
if (!gotHelloWorld) {
|
||||
assert.equal("hello world\r\n", data);
|
||||
gotHelloWorld = true;
|
||||
child.write('echo me\r\n');
|
||||
} else {
|
||||
assert.equal("echo me\r\n", data);
|
||||
gotEcho = true;
|
||||
child.close();
|
||||
}
|
||||
} else {
|
||||
puts('child end');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
process.addListener('exit', function () {
|
||||
assert.ok(gotHelloWorld);
|
||||
assert.ok(gotEcho);
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
require("../common");
|
||||
var path = require('path');
|
||||
|
||||
var sub = path.join(fixturesDir, 'print-chars.js');
|
||||
|
||||
n = 100000;
|
||||
|
||||
var child = process.createChildProcess(process.argv[0], [sub, n]);
|
||||
|
||||
var count = 0;
|
||||
|
||||
child.addListener("error", function (data){
|
||||
if (data) {
|
||||
puts("parent stderr: " + data);
|
||||
assert.ok(false);
|
||||
}
|
||||
});
|
||||
|
||||
child.addListener("output", function (data){
|
||||
if (data) {
|
||||
count += data.length;
|
||||
puts(count);
|
||||
}
|
||||
});
|
||||
|
||||
child.addListener("exit", function (data) {
|
||||
assert.equal(n, count);
|
||||
puts("okay");
|
||||
});
|
@ -1,12 +1,12 @@
|
||||
require("../common");
|
||||
tcp = require("tcp");
|
||||
net = require('net');
|
||||
var N = 50;
|
||||
|
||||
var c = 0;
|
||||
var client_recv_count = 0;
|
||||
var disconnect_count = 0;
|
||||
|
||||
var server = tcp.createServer(function (socket) {
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.addListener("connect", function () {
|
||||
socket.write("hello\r\n");
|
||||
});
|
||||
@ -20,33 +20,38 @@ var server = tcp.createServer(function (socket) {
|
||||
assert.equal(false, had_error);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT);
|
||||
|
||||
var client = tcp.createConnection(PORT);
|
||||
server.addListener('listening', function () {
|
||||
puts('listening');
|
||||
var client = net.createConnection(PORT);
|
||||
|
||||
client.setEncoding("UTF8");
|
||||
client.setEncoding("UTF8");
|
||||
|
||||
client.addListener("connect", function () {
|
||||
puts("client connected.");
|
||||
});
|
||||
client.addListener("connect", function () {
|
||||
puts("client connected.");
|
||||
});
|
||||
|
||||
client.addListener("data", function (chunk) {
|
||||
client_recv_count += 1;
|
||||
puts("client_recv_count " + client_recv_count);
|
||||
assert.equal("hello\r\n", chunk);
|
||||
client.close();
|
||||
});
|
||||
client.addListener("data", function (chunk) {
|
||||
client_recv_count += 1;
|
||||
puts("client_recv_count " + client_recv_count);
|
||||
assert.equal("hello\r\n", chunk);
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.addListener("close", function (had_error) {
|
||||
puts("disconnect");
|
||||
assert.equal(false, had_error);
|
||||
if (disconnect_count++ < N)
|
||||
client.connect(PORT); // reconnect
|
||||
else
|
||||
server.close();
|
||||
client.addListener("close", function (had_error) {
|
||||
puts("disconnect");
|
||||
assert.equal(false, had_error);
|
||||
if (disconnect_count++ < N)
|
||||
client.connect(PORT); // reconnect
|
||||
else
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
process.addListener("exit", function () {
|
||||
assert.equal(N+1, disconnect_count);
|
||||
assert.equal(N+1, client_recv_count);
|
||||
});
|
||||
|
||||
|
8
wscript
8
wscript
@ -125,8 +125,8 @@ def configure(conf):
|
||||
#if Options.options.debug:
|
||||
# conf.check(lib='profiler', uselib_store='PROFILER')
|
||||
|
||||
#if Options.options.efence:
|
||||
# conf.check(lib='efence', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='EFENCE')
|
||||
if Options.options.efence:
|
||||
conf.check(lib='efence', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='EFENCE')
|
||||
|
||||
if not conf.check(lib="execinfo", libpath=['/usr/lib', '/usr/local/lib'], uselib_store="EXECINFO"):
|
||||
# Note on Darwin/OS X: This will fail, but will still be used as the
|
||||
@ -393,6 +393,10 @@ def build(bld):
|
||||
node.target = "node"
|
||||
node.source = """
|
||||
src/node.cc
|
||||
src/node_buffer.cc
|
||||
src/node_http_parser.cc
|
||||
src/node_net2.cc
|
||||
src/node_io_watcher.cc
|
||||
src/node_child_process.cc
|
||||
src/node_constants.cc
|
||||
src/node_dns.cc
|
||||
|
Loading…
x
Reference in New Issue
Block a user