diff --git a/node.cc b/node.cc index e458775f0d..cd2fc66792 100644 --- a/node.cc +++ b/node.cc @@ -154,7 +154,7 @@ main (int argc, char *argv[]) g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction()); Init_timer(g); - //Init_tcp(g); + Init_tcp(g); Init_http(g); V8::SetFatalErrorHandler(OnFatalError); @@ -169,3 +169,4 @@ main (int argc, char *argv[]) return exit_code; } + diff --git a/node_tcp.cc b/node_tcp.cc index c4ae229805..4142bba3ab 100644 --- a/node_tcp.cc +++ b/node_tcp.cc @@ -2,7 +2,7 @@ #include "node.h" #include -#include +#include #include #include @@ -10,36 +10,42 @@ using namespace v8; +static Persistent readyState_str; -/* - Target API +static Persistent readyState_CONNECTING; +static Persistent readyState_OPEN; +static Persistent readyState_CLOSED; - TCP.connect({ +enum readyState { READY_STATE_CONNECTING = 0 + , READY_STATE_OPEN = 1 + , READY_STATE_CLOSED = 2 + } ; - host: "google.com", - - port: 80, - - connect: function () { - this.write("GET /search?q=hello HTTP/1.0\r\n\r\n"); - }, - - read: function (data) { - - request.respond("
" + data + "
"); +class TCPClient { +public: + TCPClient(Handle obj); + ~TCPClient(); - }, + int Connect(char *host, char *port); + void Write (Handle arg); + void Disconnect(); - drain: function () { - }, + void OnOpen(); - error: function () { - } - }); +private: + oi_socket socket; + struct addrinfo *address; + Persistent js_client; +}; -*/ -static oi_async thread_pool; +static void on_connect + ( oi_socket *socket + ) +{ + TCPClient *client = static_cast (socket->data); + client->OnOpen(); +} static struct addrinfo tcp_hints = /* ai_flags */ { AI_PASSIVE @@ -52,130 +58,203 @@ static struct addrinfo tcp_hints = /* ai_next */ , 0 }; -class TCPClient { -public: - oi_task resolve_task; - oi_socket socket; - struct addrinfo *address; - Persistent options; -}; - -static void on_connect - ( oi_socket *socket - ) +static Handle newTCPClient + ( const Arguments& args + ) { - TCPClient *client = static_cast (socket->data); + if (args.Length() < 1) + return Undefined(); HandleScope scope; - Handle connect_value = client->options->Get( String::NewSymbol("connect") ); - if (!connect_value->IsFunction()) - return; // error! - Handle connect_cb = Handle::Cast(connect_value); + String::AsciiValue host(args[0]); + String::AsciiValue port(args[1]); - TryCatch try_catch; - Handle r = connect_cb->Call(client->options, 0, NULL); - if (r.IsEmpty()) { - String::Utf8Value error(try_catch.Exception()); - printf("connect error: %s\n", *error); - } + 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 void resolve_done - ( oi_task *resolve_task - , int result - ) +static TCPClient* +UnwrapClient (Handle obj) { - TCPClient *client = static_cast (resolve_task->data); - - printf("hello world\n"); + HandleScope scope; + Handle field = Handle::Cast(obj->GetInternalField(0)); + TCPClient* client = static_cast(field->Value()); + return client; +} - if(result != 0) { - printf("error. TODO make call options error callback\n"); - client->options.Dispose(); - delete client; - return; - } +static Handle +WriteCallback (const Arguments& args) +{ + HandleScope scope; + TCPClient *client = UnwrapClient(args.Holder()); + client->Write(args[0]); + return Undefined(); +} - printf("Got the address succesfully. Let's connect now. \n"); +static Handle +DisconnectCallback (const Arguments& args) +{ + HandleScope scope; + TCPClient *client = UnwrapClient(args.Holder()); + client->Disconnect(); + return Undefined(); +} - oi_socket_init(&client->socket, 30.0); // TODO adjustable timeout +static void +client_destroy (Persistent _, void *data) +{ + TCPClient *client = static_cast (data); + delete client; +} - client->socket.on_connect = on_connect; - client->socket.on_read = NULL; - client->socket.on_drain = NULL; - client->socket.on_error = NULL; - client->socket.on_close = NULL; - client->socket.on_timeout = NULL; - client->socket.data = 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; - oi_socket_connect (&client->socket, client->address); - oi_socket_attach (&client->socket, node_loop()); + HandleScope scope; + js_client = Persistent::New(_js_client); + js_client->SetInternalField (0, External::New(this)); + js_client.MakeWeak (this, client_destroy); +} - freeaddrinfo(client->address); - client->address = NULL; +TCPClient::~TCPClient () +{ + Disconnect(); + oi_socket_detach (&socket); + js_client.Dispose(); + js_client.Clear(); // necessary? } -static Handle Connect - ( const Arguments& args - ) +int +TCPClient::Connect(char *host, char *port) { - if (args.Length() < 1) - return Undefined(); + int r; HandleScope scope; - Handle arg = args[0]; - Handle options = arg->ToObject(); + js_client->Set(readyState_str, readyState_CONNECTING); - /* Make sure the user has provided at least host and port */ + /* 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; + } - Handle host_value = options->Get( String::NewSymbol("host") ); + r = oi_socket_connect (&socket, address); + if(r != 0) { + perror("oi_socket_connect"); + return r; + } + oi_socket_attach (&socket, node_loop()); - if(host_value->IsUndefined()) - return False(); + freeaddrinfo(address); + address = NULL; +} - Handle port_value = options->Get( String::NewSymbol("port") ); +void TCPClient::Write (Handle arg) +{ + HandleScope scope; - if(port_value->IsUndefined()) - return False(); + if(arg == Null()) { - Handle host = host_value->ToString(); - Handle port = port_value->ToString(); + oi_socket_write_eof(&socket); - char host_s[host->Length()+1]; // + 1 for \0 - char port_s[port->Length()+1]; + } else { + Local s = arg->ToString(); - host->WriteAscii(host_s); - port->WriteAscii(port_s); + oi_buf *buf = oi_buf_new2(s->Length()); + s->WriteAscii(buf->base, 0, s->Length()); - printf("resolving host: %s, port: %s\n", host_s, port_s); + oi_socket_write(&socket, buf); + } +} + +void +TCPClient::Disconnect() +{ + oi_socket_close(&socket); +} + +void TCPClient::OnOpen() +{ + HandleScope scope; - TCPClient *client = new TCPClient; + js_client->Set(readyState_str, readyState_OPEN); - oi_task_init_getaddrinfo ( &client->resolve_task - , resolve_done - , host_s - , port_s - , &tcp_hints - , &client->address - ); - client->options = Persistent::New(options); + Handle onopen_value = js_client->Get( String::NewSymbol("onopen") ); + if (!onopen_value->IsFunction()) + return; + Handle onopen = Handle::Cast(onopen_value); - oi_async_submit (&thread_pool, &client->resolve_task); + 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) { - oi_async_init(&thread_pool); - oi_async_attach(node_loop(), &thread_pool); - 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); - Local tcp = Object::New(); - target->Set(String::NewSymbol("TCP"), tcp); + client_t->InstanceTemplate()->Set ( String::NewSymbol("disconnect") + , disconnect_t->GetFunction() + ); - tcp->Set(String::NewSymbol("connect"), FunctionTemplate::New(Connect)->GetFunction()); + target->Set(String::NewSymbol("TCPClient"), client_t->GetFunction()); } diff --git a/spec/index.html b/spec/index.html index 0d61613de2..e0a1e63a85 100644 --- a/spec/index.html +++ b/spec/index.html @@ -64,22 +64,33 @@

Unless otherwise noted, all functions can be considered non-blocking. Non-blocking means that program execution will continue - without waiting for I/O (be that network or device). + without waiting for some I/O event (be that network or device). + +

1.1 The event loop

-

... +

The program is run event loop. There are no concurrent + operations. As long as there are pending events the program will continue + running. If however there arn't any pending callbacks waiting for + something to happen, the program will exit. + +

Only one callback is executed at a time. +

1.2 Execution context

+ +

Global data is shared between callbacks. -

... +

+ spawn() to start a new context/event loop?

2 HTTP Server

3 TCP Client

-
interface TCP.Client  {
+  
interface TCPClient  {
   readonly attribute DOMString host;
-  readonly attribute DOMString port;
+  readonly attribute DOMString port;
 
   // ready state
   const unsigned short CONNECTING = 0;
@@ -89,16 +100,60 @@
 
   // networking                
            attribute Function onopen;
-           attribute Function onrecv;
+           attribute Function onread;
            attribute Function onclose;
-  void send(in DOMString data);
+  void write(in DOMString data);
   void disconnect();           
 };
+ +

When a TCPClient object is + created, the the interpreter must try to establish a connection. + +

The host + attribute is the domain name of the network connection. The port attribute identifies the + port. + +

The readyState attribute + represents the state of the connection. When the object is created it must + be set to CONNECTING. + +

Once a connection is established, the readyState +attribute's value must be changed to OPEN, and the onopen callback will be +made. + +

When data is received, the onread callback will be made.

+ + + +

When the connection is closed, the readyState +attribute's value must be changed to CLOSED, and the onclose callback +will be made. + +

The write() + method transmits data using the connection. If the connection is not yet + established, it must raise an INVALID_STATE_ERR exception. + +

The disconnect() method + must close the connection, if it is open. If the connection is already + closed, it must do nothing. Closing the connection causes a onclose callback to be +made and the readyState attribute's value to change, as described above. + +

4 Timers

-

Timers and Intervals allow one to schedule an event at a later date. +

Timers allow one to schedule an event at a later date. There are four globally exposed functions setTimeout, clearTimeout, diff --git a/tcp_example.js b/tcp_example.js index 8940b85a02..cf1504f4b5 100644 --- a/tcp_example.js +++ b/tcp_example.js @@ -1,9 +1,45 @@ +/* +[Constructor(in DOMString url)] +interface TCP.Client { + readonly attribute DOMString host; + readonly attribute DOMString port; + + // ready state + const unsigned short CONNECTING = 0; + const unsigned short OPEN = 1; + const unsigned short CLOSED = 2; + readonly attribute long readyState; + + // networking + attribute Function onopen; + attribute Function onrecv; + attribute Function onclose; + void send(in DOMString data); + void disconnect(); +}; +*/ + +client = new TCPClient("localhost", 11222); + +log("readyState: " + client.readyState); +//assertEqual(client.readystate, TCP.CONNECTING); + +client.onopen = function () { + log("connected to dynomite"); + log("readyState: " + client.readyState); + client.write("get 1 /\n"); +}; + +client.onread = function (chunk) { + log(chunk); +}; + +client.onclose = function () { + log("connection closed"); +}; + +setTimeout(function () { + client.disconnect(); +}, 1000); -TCP.connect ({ - host: "google.com", - port: 80, - connected: function () { - log("connected to google.com"); - } -});