#include "node_tcp.h" #include "node.h" #include #include #include #include #include using namespace v8; static Persistent readyState_str; static Persistent readyState_CONNECTING; static Persistent readyState_OPEN; static Persistent readyState_CLOSED; enum readyState { READY_STATE_CONNECTING = 0 , READY_STATE_OPEN = 1 , READY_STATE_CLOSED = 2 } ; class TCPClient { public: TCPClient(Handle obj); ~TCPClient(); int Connect(char *host, char *port); void Write (Handle arg); void Disconnect(); void OnOpen(); private: oi_socket socket; struct addrinfo *address; Persistent js_client; }; static void on_connect ( oi_socket *socket ) { TCPClient *client = static_cast (socket->data); client->OnOpen(); } static struct addrinfo tcp_hints = /* ai_flags */ { AI_PASSIVE /* ai_family */ , AF_UNSPEC /* ai_socktype */ , SOCK_STREAM /* ai_protocol */ , 0 /* ai_addrlen */ , 0 /* ai_addr */ , 0 /* ai_canonname */ , 0 /* ai_next */ , 0 }; static Handle newTCPClient ( const Arguments& args ) { if (args.Length() < 1) return Undefined(); HandleScope scope; String::AsciiValue host(args[0]); String::AsciiValue port(args[1]); TCPClient *client = new TCPClient(args.This()); if(client == NULL) return Undefined(); // XXX raise error? int r = client->Connect(*host, *port); if (r != 0) return Undefined(); // XXX raise error? return args.This(); } static TCPClient* UnwrapClient (Handle obj) { HandleScope scope; Handle field = Handle::Cast(obj->GetInternalField(0)); TCPClient* client = static_cast(field->Value()); return client; } static Handle WriteCallback (const Arguments& args) { HandleScope scope; TCPClient *client = UnwrapClient(args.Holder()); client->Write(args[0]); return Undefined(); } static Handle DisconnectCallback (const Arguments& args) { HandleScope scope; TCPClient *client = UnwrapClient(args.Holder()); client->Disconnect(); return Undefined(); } static void client_destroy (Persistent _, void *data) { TCPClient *client = static_cast (data); delete client; } TCPClient::TCPClient(Handle _js_client) { oi_socket_init(&socket, 30.0); // TODO adjustable timeout socket.on_connect = on_connect; socket.on_read = NULL; socket.on_drain = NULL; socket.on_error = NULL; socket.on_close = NULL; socket.on_timeout = NULL; socket.data = this; HandleScope scope; js_client = Persistent::New(_js_client); js_client->SetInternalField (0, External::New(this)); js_client.MakeWeak (this, client_destroy); } TCPClient::~TCPClient () { Disconnect(); oi_socket_detach (&socket); js_client.Dispose(); js_client.Clear(); // necessary? } int TCPClient::Connect(char *host, char *port) { int r; HandleScope scope; js_client->Set(readyState_str, readyState_CONNECTING); /* FIXME Blocking DNS resolution. Use oi_async. */ printf("resolving host: %s, port: %s\n", host, port); r = getaddrinfo (host, port, &tcp_hints, &address); if(r != 0) { perror("getaddrinfo"); return r; } r = oi_socket_connect (&socket, address); if(r != 0) { perror("oi_socket_connect"); return r; } oi_socket_attach (&socket, node_loop()); freeaddrinfo(address); address = NULL; } void TCPClient::Write (Handle arg) { HandleScope scope; if(arg == Null()) { oi_socket_write_eof(&socket); } else { Local s = arg->ToString(); oi_buf *buf = oi_buf_new2(s->Length()); s->WriteAscii(buf->base, 0, s->Length()); oi_socket_write(&socket, buf); } } void TCPClient::Disconnect() { oi_socket_close(&socket); } void TCPClient::OnOpen() { HandleScope scope; js_client->Set(readyState_str, readyState_OPEN); Handle onopen_value = js_client->Get( String::NewSymbol("onopen") ); if (!onopen_value->IsFunction()) return; Handle onopen = Handle::Cast(onopen_value); TryCatch try_catch; Handle r = onopen->Call(js_client, 0, NULL); if(try_catch.HasCaught()) node_fatal_exception(try_catch); } void Init_tcp (Handle target) { HandleScope scope; readyState_str = Persistent::New(String::NewSymbol("readyState")); Local client_t = FunctionTemplate::New(newTCPClient); client_t->InstanceTemplate()->SetInternalFieldCount(1); /* readyState constants */ readyState_CONNECTING = Persistent::New(Integer::New(READY_STATE_CONNECTING)); client_t->InstanceTemplate()->Set ( String::NewSymbol("CONNECTING") , readyState_CONNECTING ); readyState_OPEN = Persistent::New(Integer::New(READY_STATE_OPEN)); client_t->InstanceTemplate()->Set ( String::NewSymbol("OPEN") , readyState_OPEN ); readyState_CLOSED = Persistent::New(Integer::New(READY_STATE_CLOSED)); client_t->InstanceTemplate()->Set ( String::NewSymbol("CLOSED") , readyState_CLOSED ); /* write callback */ Local write_t = FunctionTemplate::New(WriteCallback); client_t->InstanceTemplate()->Set ( String::NewSymbol("write") , write_t->GetFunction() ); /* disconnect callback */ Local disconnect_t = FunctionTemplate::New(DisconnectCallback); client_t->InstanceTemplate()->Set ( String::NewSymbol("disconnect") , disconnect_t->GetFunction() ); target->Set(String::NewSymbol("TCPClient"), client_t->GetFunction()); }