#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
/* 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());
}
<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>,