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:
Ryan Dahl 2010-03-19 21:51:50 -07:00
commit cc053e7df7
69 changed files with 5566 additions and 1022 deletions

View File

@ -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");

View File

@ -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
View 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
View 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();
};

View File

@ -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
View 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
View 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");
}
};

View File

@ -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.");

View File

@ -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.

View File

@ -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
View 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;
};

View File

@ -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

View File

@ -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_

View File

@ -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
View 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
View 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_

View File

@ -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

View File

@ -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_

View File

@ -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));

View File

@ -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
View 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
View 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

View File

@ -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
View 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
View 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

File diff suppressed because it is too large Load Diff

12
src/node_net2.h Normal file
View 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

View File

@ -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

View File

@ -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

View File

@ -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();
}

View File

@ -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
View 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);

View 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
View File

@ -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();
});

View 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);

View File

@ -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);

View File

@ -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);
});

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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;

View File

@ -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;

View 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));

View File

@ -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);

View File

@ -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 () {

View 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);
});

View 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);
});

View 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);
});

View 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);
});

View 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");
});

View File

@ -1,5 +1,5 @@
require("../common");
var exec = require('child_process').exec;
success_count = 0;
error_count = 0;

View File

@ -60,4 +60,4 @@ process.addListener('exit', function() {
for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}
});
});

View File

@ -59,4 +59,4 @@ process.addListener('exit', function() {
for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}
});
});

View File

@ -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");

View File

@ -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();

View File

@ -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);
});

View File

@ -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();
});

View File

@ -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();

View 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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

View 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');
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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");
});

View File

@ -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);
});

View File

@ -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