half way implemented the new TCPClient
authorRyan <ry@tinyclouds.org>
Thu, 5 Mar 2009 12:41:10 +0000 (13:41 +0100)
committerRyan <ry@tinyclouds.org>
Thu, 5 Mar 2009 12:41:10 +0000 (13:41 +0100)
node.cc
node_tcp.cc
spec/index.html
tcp_example.js

diff --git a/node.cc b/node.cc
index e458775..cd2fc66 100644 (file)
--- 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;
 }
+
index c4ae229..4142bba 100644 (file)
@@ -2,7 +2,7 @@
 #include "node.h"
 
 #include <oi_socket.h>
-#include <oi_async.h>
+#include <oi_buf.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 using namespace v8;
 
+static Persistent<String> readyState_str; 
 
-/*
-  Target API
+static Persistent<Integer> readyState_CONNECTING; 
+static Persistent<Integer> readyState_OPEN; 
+static Persistent<Integer> 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("<table> <td>" + data + "</td> </table>");
+class TCPClient {
+public:
+  TCPClient(Handle<Object> obj);
+  ~TCPClient();
 
-      },
+  int Connect(char *host, char *port);
+  void Write (Handle<Value> arg);
+  void Disconnect();
 
-      drain: function () {
-      }, 
+  void OnOpen();
 
-      error: function () {
-      } 
-  });
+private:
+  oi_socket socket;
+  struct addrinfo *address;
+  Persistent<Object> js_client;
+};
 
-*/
 
-static oi_async thread_pool;
+static void on_connect
+  ( oi_socket *socket
+  )
+{
+  TCPClient *client = static_cast<TCPClient*> (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<Object> options;
-};
-
-static void on_connect
-  ( oi_socket *socket
-  )
+static Handle<Value> newTCPClient
+  ( const Arguments& args
+  ) 
 {
-  TCPClient *client = static_cast<TCPClient*> (socket->data);
+  if (args.Length() < 1)
+    return Undefined();
 
   HandleScope scope;
 
-  Handle<Value> connect_value = client->options->Get( String::NewSymbol("connect") );
-  if (!connect_value->IsFunction())
-    return; // error!
-  Handle<Function> connect_cb = Handle<Function>::Cast(connect_value);
+  String::AsciiValue host(args[0]);
+  String::AsciiValue port(args[1]);
 
-  TryCatch try_catch;
-  Handle<Value> 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<Object> obj) 
 {
-  TCPClient *client = static_cast<TCPClient*> (resolve_task->data);
-
-  printf("hello world\n");
+  HandleScope scope;
+  Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
+  TCPClient* client = static_cast<TCPClient*>(field->Value());
+  return client;
+}
 
-  if(result != 0) {
-    printf("error. TODO make call options error callback\n");
-    client->options.Dispose();
-    delete client;
-    return;
-  }
+static Handle<Value>
+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<Value>
+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<Value> _, void *data)
+{
+  TCPClient *client = static_cast<TCPClient *> (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<Object> _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<Object>::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<Value> Connect
-  ( const Arguments& args
-  ) 
+int
+TCPClient::Connect(char *host, char *port)
 {
-  if (args.Length() < 1)
-    return Undefined();
+  int r;
 
   HandleScope scope;
 
-  Handle<Value> arg = args[0];
-  Handle<Object> 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<Value> 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<Value> port_value = options->Get( String::NewSymbol("port") );
+void TCPClient::Write (Handle<Value> arg)
+{
+  HandleScope scope;
 
-  if(port_value->IsUndefined()) 
-    return False();    
+  if(arg == Null()) {
 
-  Handle<String> host = host_value->ToString();
-  Handle<String> 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<String> 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<Object>::New(options); 
+  Handle<Value> onopen_value = js_client->Get( String::NewSymbol("onopen") );
+  if (!onopen_value->IsFunction())
+    return; 
+  Handle<Function> onopen = Handle<Function>::Cast(onopen_value);
 
-  oi_async_submit (&thread_pool, &client->resolve_task);
+  TryCatch try_catch;
+
+  Handle<Value> r = onopen->Call(js_client, 0, NULL);
+
+  if(try_catch.HasCaught())
+    node_fatal_exception(try_catch);
 }
 
 void
 Init_tcp (Handle<Object> target)
 {
-  oi_async_init(&thread_pool);
-  oi_async_attach(node_loop(), &thread_pool);
-
   HandleScope scope;
+  readyState_str = Persistent<String>::New(String::NewSymbol("readyState"));
+
+  Local<FunctionTemplate> client_t = FunctionTemplate::New(newTCPClient);
+
+  client_t->InstanceTemplate()->SetInternalFieldCount(1);
+
+  /* readyState constants */
+
+  readyState_CONNECTING = Persistent<Integer>::New(Integer::New(READY_STATE_CONNECTING));
+  client_t->InstanceTemplate()->Set ( String::NewSymbol("CONNECTING")
+                                    , readyState_CONNECTING
+                                    );
+
+  readyState_OPEN = Persistent<Integer>::New(Integer::New(READY_STATE_OPEN));
+  client_t->InstanceTemplate()->Set ( String::NewSymbol("OPEN")
+                                    , readyState_OPEN
+                                    );
+
+  readyState_CLOSED = Persistent<Integer>::New(Integer::New(READY_STATE_CLOSED));
+  client_t->InstanceTemplate()->Set ( String::NewSymbol("CLOSED")
+                                    , readyState_CLOSED
+                                    );
+
+  /* write callback */
+
+  Local<FunctionTemplate> write_t = FunctionTemplate::New(WriteCallback);
+
+  client_t->InstanceTemplate()->Set ( String::NewSymbol("write")
+                                    , write_t->GetFunction()
+                                    );
+
+  /* disconnect callback */
+
+  Local<FunctionTemplate> disconnect_t = FunctionTemplate::New(DisconnectCallback);
 
-  Local<Object> 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());
 }
 
index 0d61613..e0a1e63 100644 (file)
   
   <p>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).
+
+  
 
   <h3 id=the-event-loop><span class=secno>1.1 </span>The event loop</h3>
 
-  <p>... 
+  <p>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. 
+
+  <p>Only one callback is executed at a time. 
+
 
   <h3 id=execution-context><span class=secno>1.2 </span>Execution context</h3>
+
+  <p>Global data is shared between callbacks. 
   
-  <p>... 
+  <p> 
+    <code>spawn()</code> to start a new context/event loop? 
 
   <h2 id=http_server><span class=secno>2 </span>HTTP Server</h2>
 
   <h2 id=tcp_client><span class=secno>3 </span>TCP Client</h2>
-  <pre class=idl>interface <dfn id=tcpclient>TCP.Client</dfn>  {
+  <pre class=idl>interface <dfn id=tcpclient>TCPClient</dfn>  {
   readonly attribute DOMString <a href="index.html#host" title=dom-TCPCleint-host>host</a>;
-  readonly attribute DOMString <a href="index.html#port" title=dom-TCPCleint-host>port</a>;
+  readonly attribute DOMString <a href="index.html#port" title=dom-TCPCleint-port>port</a>;
 
   // ready state
   const unsigned short CONNECTING = 0;
 
   // 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();           
 };</pre>
 
+
+  </p><p>When a <code><a href="#connection0">TCPClient</a></code> object is
+   created, the the interpreter must try to establish a connection.
+
+  </p><p>The <dfn id="host" title="dom-TCPClient-host"><code>host</code></dfn>
+  attribute is the domain name of the network connection. The <dfn id="port"
+  title="dom-Connection-port"><code>port</code></dfn> attribute identifies the
+  port.
+
+  </p><p>The <dfn id="readystate0" title="dom-Connection-readyState"><code>readyState</code></dfn> attribute
+   represents the state of the connection. When the object is created it must
+   be set to <code>CONNECTING</code>.
+
+  <p id="openConnection">Once a connection is established, the <code
+title="dom-Connection-readyState"><a href="#readystate0">readyState</a></code>
+attribute's value must be changed to <code>OPEN</code>, and the <code
+title="event-connection-open"><a href="#onopen">onopen</a></code> callback will be
+made.
+
+  </p><p>When data is received, the <code title="event-connection-read"><a
+href="#onread">onread</a></code> callback will be made.</p>
+
+  <!-- conf crit for this
+  statement is in the various protocol-specific sections below. -->
+
+  <p id="closeConnection">When the connection is closed, the <code
+title="dom-Connection-readyState"><a href="#readystate0">readyState</a></code>
+attribute's value must be changed to <code>CLOSED</code>, and the <code
+title="event-connection-close"><a href="#onclose">onclose</a></code> callback
+will be made.
+
+  </p><p>The <dfn id="write" title="dom-Connection-write"><code>write()</code></dfn>
+   method transmits data using the connection. If the connection is not yet
+   established, it must raise an <code>INVALID_STATE_ERR</code> exception. 
+
+  </p><p>The <dfn id="disconnect" title="dom-Connection-disconnect"><code>disconnect()</code></dfn> method
+   must close the connection, if it is open. If the connection is already
+   closed, it must do nothing. Closing the connection causes a <code
+title="event-connection-close"><a href="#onclose">onclose</a></code> callback to be
+made and the <code title="dom-Connection-readyState"><a
+href="#readystate0">readyState</a></code> attribute's value to change, as <a
+href="#closeConnection">described above</a>.
+
+
   <h2 id=timers><span class=secno>4 </span>Timers</h2>
 
 
-  <p>Timers and Intervals allow one to schedule an event at a later date. 
+  <p>Timers allow one to schedule an event at a later date. 
      There are four globally exposed functions 
       <code>setTimeout</code>, 
       <code>clearTimeout</code>,
index 8940b85..cf1504f 100644 (file)
@@ -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");
-  }
-});