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("");
+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");
- }
-});