|
@ -47,14 +47,18 @@ static const struct addrinfo client_tcp_hints = |
|
|
|
|
|
|
|
|
Persistent<FunctionTemplate> Connection::constructor_template; |
|
|
Persistent<FunctionTemplate> Connection::constructor_template; |
|
|
|
|
|
|
|
|
|
|
|
// Initialize the tcp.Connection object.
|
|
|
void Connection::Initialize(v8::Handle<v8::Object> target) { |
|
|
void Connection::Initialize(v8::Handle<v8::Object> target) { |
|
|
HandleScope scope; |
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New); |
|
|
Local<FunctionTemplate> t = FunctionTemplate::New(New); |
|
|
|
|
|
|
|
|
constructor_template = Persistent<FunctionTemplate>::New(t); |
|
|
constructor_template = Persistent<FunctionTemplate>::New(t); |
|
|
|
|
|
// Inherits from node.EventEmitter
|
|
|
constructor_template->Inherit(EventEmitter::constructor_template); |
|
|
constructor_template->Inherit(EventEmitter::constructor_template); |
|
|
|
|
|
// All native node objects have 1 internal field (required by ObjectWrap)
|
|
|
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); |
|
|
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); |
|
|
|
|
|
// Set class name for debugging output
|
|
|
constructor_template->SetClassName(String::NewSymbol("Connection")); |
|
|
constructor_template->SetClassName(String::NewSymbol("Connection")); |
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "connect", Connect); |
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "connect", Connect); |
|
@ -67,16 +71,19 @@ void Connection::Initialize(v8::Handle<v8::Object> target) { |
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout); |
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout); |
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setNoDelay", SetNoDelay); |
|
|
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setNoDelay", SetNoDelay); |
|
|
|
|
|
|
|
|
|
|
|
// Getter for connection.readyState
|
|
|
constructor_template->PrototypeTemplate()->SetAccessor( |
|
|
constructor_template->PrototypeTemplate()->SetAccessor( |
|
|
READY_STATE_SYMBOL, |
|
|
READY_STATE_SYMBOL, |
|
|
ReadyStateGetter); |
|
|
ReadyStateGetter); |
|
|
|
|
|
|
|
|
|
|
|
// Assign class to its place as tcp.Connection
|
|
|
target->Set(String::NewSymbol("Connection"), |
|
|
target->Set(String::NewSymbol("Connection"), |
|
|
constructor_template->GetFunction()); |
|
|
constructor_template->GetFunction()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Handle<Value> Connection::ReadyStateGetter(Local<String> property, |
|
|
Handle<Value> Connection::ReadyStateGetter(Local<String> property, |
|
|
const AccessorInfo& info) { |
|
|
const AccessorInfo& info) { |
|
|
|
|
|
// Unwrap the javascript object to get the C++ object
|
|
|
Connection *connection = ObjectWrap::Unwrap<Connection>(info.This()); |
|
|
Connection *connection = ObjectWrap::Unwrap<Connection>(info.This()); |
|
|
assert(connection); |
|
|
assert(connection); |
|
|
|
|
|
|
|
@ -84,8 +91,12 @@ Handle<Value> Connection::ReadyStateGetter(Local<String> property, |
|
|
|
|
|
|
|
|
assert(property == READY_STATE_SYMBOL); |
|
|
assert(property == READY_STATE_SYMBOL); |
|
|
|
|
|
|
|
|
|
|
|
// Resolving is not done in evcom, it's done in this file. Thus we add
|
|
|
|
|
|
// this "opening" symbol to the native EVCOM ready states. "opening"
|
|
|
|
|
|
// really means "resolving".
|
|
|
if (connection->resolving_) return scope.Close(OPENING_SYMBOL); |
|
|
if (connection->resolving_) return scope.Close(OPENING_SYMBOL); |
|
|
|
|
|
|
|
|
|
|
|
// Map between the evcom enum and V8 strings:
|
|
|
switch (evcom_stream_state(&connection->stream_)) { |
|
|
switch (evcom_stream_state(&connection->stream_)) { |
|
|
case EVCOM_INITIALIZED: return scope.Close(CLOSED_SYMBOL); |
|
|
case EVCOM_INITIALIZED: return scope.Close(CLOSED_SYMBOL); |
|
|
case EVCOM_CONNECTING: return scope.Close(OPENING_SYMBOL); |
|
|
case EVCOM_CONNECTING: return scope.Close(OPENING_SYMBOL); |
|
@ -101,6 +112,9 @@ Handle<Value> Connection::ReadyStateGetter(Local<String> property, |
|
|
String::New("This shouldn't happen."))); |
|
|
String::New("This shouldn't happen."))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Constructor - these actions are not taken in the normal constructor
|
|
|
|
|
|
// (Connection::Connection) because sometimes the Connection needs to be
|
|
|
|
|
|
// reinitialized without destroying the object.
|
|
|
void Connection::Init() { |
|
|
void Connection::Init() { |
|
|
resolving_ = false; |
|
|
resolving_ = false; |
|
|
evcom_stream_init(&stream_); |
|
|
evcom_stream_init(&stream_); |
|
@ -116,27 +130,39 @@ Connection::~Connection() { |
|
|
assert(stream_.sendfd < 0 && "garbage collecting open Connection"); |
|
|
assert(stream_.sendfd < 0 && "garbage collecting open Connection"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// V8 contstructor
|
|
|
Handle<Value> Connection::New(const Arguments& args) { |
|
|
Handle<Value> Connection::New(const Arguments& args) { |
|
|
HandleScope scope; |
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
// All constructors in node look similar to this.
|
|
|
|
|
|
|
|
|
|
|
|
// allocate the C++ object
|
|
|
Connection *connection = new Connection(); |
|
|
Connection *connection = new Connection(); |
|
|
|
|
|
|
|
|
|
|
|
// Use ObjectWrap::Wrap to assign it to the internal field in the V8
|
|
|
|
|
|
// object.
|
|
|
connection->Wrap(args.This()); |
|
|
connection->Wrap(args.This()); |
|
|
|
|
|
|
|
|
return args.This(); |
|
|
return args.This(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Open a connection. Starts resolving the hostname in the libeio
|
|
|
|
|
|
// thread pool, when that completes in Connection::AfterResolve, actually
|
|
|
|
|
|
// open the connection.
|
|
|
Handle<Value> Connection::Connect(const Arguments& args) { |
|
|
Handle<Value> Connection::Connect(const Arguments& args) { |
|
|
|
|
|
// Unwrap V8 object into C++ object
|
|
|
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); |
|
|
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); |
|
|
|
|
|
|
|
|
assert(connection); |
|
|
assert(connection); |
|
|
|
|
|
|
|
|
HandleScope scope; |
|
|
HandleScope scope; |
|
|
|
|
|
|
|
|
|
|
|
// If the connection is closed, reinitialize it.
|
|
|
if (connection->ReadyState() == EVCOM_CLOSED) { |
|
|
if (connection->ReadyState() == EVCOM_CLOSED) { |
|
|
connection->Init(); // in case we're reusing the socket
|
|
|
connection->Init(); // in case we're reusing the socket
|
|
|
assert(connection->ReadyState() == EVCOM_INITIALIZED); |
|
|
assert(connection->ReadyState() == EVCOM_INITIALIZED); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// If connect() is called on an open connection, raise an error.
|
|
|
if (connection->ReadyState() != EVCOM_INITIALIZED) { |
|
|
if (connection->ReadyState() != EVCOM_INITIALIZED) { |
|
|
Local<Value> exception = Exception::Error( |
|
|
Local<Value> exception = Exception::Error( |
|
|
String::New("Socket is not in CLOSED state.")); |
|
|
String::New("Socket is not in CLOSED state.")); |
|
@ -146,39 +172,51 @@ Handle<Value> Connection::Connect(const Arguments& args) { |
|
|
assert(connection->stream_.recvfd < 0); |
|
|
assert(connection->stream_.recvfd < 0); |
|
|
assert(connection->stream_.sendfd < 0); |
|
|
assert(connection->stream_.sendfd < 0); |
|
|
|
|
|
|
|
|
|
|
|
// Make sure we have at least one argument (a port)
|
|
|
if (args.Length() == 0) { |
|
|
if (args.Length() == 0) { |
|
|
Local<Value> exception = Exception::TypeError( |
|
|
Local<Value> exception = Exception::TypeError( |
|
|
String::New("First argument must be a port number")); |
|
|
String::New("First argument must be a port number")); |
|
|
return ThrowException(exception); |
|
|
return ThrowException(exception); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// We need the port as a string for getaddrinfo().
|
|
|
|
|
|
// Thus cast it to a string. The strdup()'d value will be freed in
|
|
|
|
|
|
// Resolve().
|
|
|
String::AsciiValue port_sv(args[0]->ToString()); |
|
|
String::AsciiValue port_sv(args[0]->ToString()); |
|
|
assert(connection->port_ == NULL); |
|
|
assert(connection->port_ == NULL); |
|
|
connection->port_ = strdup(*port_sv); |
|
|
connection->port_ = strdup(*port_sv); |
|
|
|
|
|
|
|
|
|
|
|
// Get the host, if any is present.
|
|
|
assert(connection->host_ == NULL); |
|
|
assert(connection->host_ == NULL); |
|
|
if (args.Length() > 1 && args[1]->IsString()) { |
|
|
if (args.Length() > 1 && args[1]->IsString()) { |
|
|
String::Utf8Value host_sv(args[1]->ToString()); |
|
|
String::Utf8Value host_sv(args[1]->ToString()); |
|
|
connection->host_ = strdup(*host_sv); |
|
|
connection->host_ = strdup(*host_sv); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
connection->resolving_ = true; |
|
|
connection->resolving_ = true; // This is done so that readyState can return
|
|
|
|
|
|
// "opening" while getaddinfo() runs.
|
|
|
|
|
|
|
|
|
|
|
|
// There will not be any active watchers from this object on the event
|
|
|
|
|
|
// loop while getaddrinfo() runs. If the only thing happening in the
|
|
|
|
|
|
// script was this hostname resolution, then the event loop would drop
|
|
|
|
|
|
// out. Thus we need to add ev_ref() until AfterResolve().
|
|
|
ev_ref(EV_DEFAULT_UC); |
|
|
ev_ref(EV_DEFAULT_UC); |
|
|
|
|
|
|
|
|
|
|
|
// Attach the object so it doesn't get garbage collected.
|
|
|
connection->Attach(); |
|
|
connection->Attach(); |
|
|
|
|
|
|
|
|
/* For the moment I will do DNS lookups in the eio thread pool. This is
|
|
|
// For the moment I will do DNS lookups in the eio thread pool. This is
|
|
|
* sub-optimal and cannot handle massive numbers of requests. |
|
|
// sub-optimal and cannot handle massive numbers of requests.
|
|
|
* In the future I will move to a system using adns or udns: |
|
|
// In the future I will move to a system using adns or udns:
|
|
|
* http://lists.schmorp.de/pipermail/libev/2009q1/000632.html
|
|
|
// http://lists.schmorp.de/pipermail/libev/2009q1/000632.html
|
|
|
*/ |
|
|
|
|
|
eio_custom(Connection::Resolve, EIO_PRI_DEFAULT, Connection::AfterResolve, |
|
|
eio_custom(Connection::Resolve, EIO_PRI_DEFAULT, Connection::AfterResolve, |
|
|
connection); |
|
|
connection); |
|
|
|
|
|
|
|
|
return Undefined(); |
|
|
return Undefined(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// This function is executed in the thread pool. It cannot modify any
|
|
|
|
|
|
// members of the connection object.
|
|
|
int Connection::Resolve(eio_req *req) { |
|
|
int Connection::Resolve(eio_req *req) { |
|
|
Connection *connection = static_cast<Connection*> (req->data); |
|
|
Connection *connection = static_cast<Connection*> (req->data); |
|
|
struct addrinfo *address = NULL; |
|
|
struct addrinfo *address = NULL; |
|
|