#include "node_tcp.h" #include "node.h" #include #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(); void OnRead(const void *buf, size_t count); void OnClose(); private: int ReadyState(); 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 void on_close (oi_socket *socket) { TCPClient *client = static_cast (socket->data); client->OnClose(); } static void on_read (oi_socket *socket, const void *buf, size_t count) { TCPClient *client = static_cast (socket->data); client->OnRead(buf, count); } 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; char *host = NULL; String::AsciiValue host_v(args[0]->ToString()); if(args[0]->IsString()) { host = *host_v; } 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 = on_read; socket.on_drain = NULL; socket.on_error = NULL; socket.on_close = on_close; socket.on_timeout = on_close; 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; // // TODO if ReadyState() is not READY_STATE_OPEN then raise INVALID_STATE_ERR // 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); } } int TCPClient::ReadyState() { return js_client->Get(readyState_str)->IntegerValue(); } void TCPClient::Disconnect() { oi_socket_close(&socket); } void TCPClient::OnOpen() { HandleScope scope; assert(READY_STATE_CONNECTING == ReadyState()); 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 TCPClient::OnRead(const void *buf, size_t count) { HandleScope scope; assert(READY_STATE_OPEN == ReadyState()); Handle onread_value = js_client->Get( String::NewSymbol("onread") ); if (!onread_value->IsFunction()) return; Handle onread = Handle::Cast(onread_value); const int argc = 1; Handle argv[argc]; if(count) { Handle chunk = String::New((const char*)buf, count); // TODO binary data? argv[0] = chunk; } else { // TODO eof? delete write method? argv[0] = Null(); } TryCatch try_catch; Handle r = onread->Call(js_client, argc, argv); if(try_catch.HasCaught()) node_fatal_exception(try_catch); } void TCPClient::OnClose() { HandleScope scope; assert(READY_STATE_OPEN == ReadyState()); js_client->Set(readyState_str, readyState_CLOSED); Handle onclose_value = js_client->Get( String::NewSymbol("onclose") ); if (!onclose_value->IsFunction()) return; Handle onclose = Handle::Cast(onclose_value); TryCatch try_catch; Handle r = onclose->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->Set ("CONNECTING", readyState_CONNECTING); readyState_OPEN = Persistent::New(Integer::New(READY_STATE_OPEN)); client_t->Set ("OPEN", readyState_OPEN); readyState_CLOSED = Persistent::New(Integer::New(READY_STATE_CLOSED)); client_t->Set ("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()); }