Browse Source

half way implemented the new TCPClient

v0.7.4-release
Ryan 16 years ago
parent
commit
93c7e88c87
  1. 3
      node.cc
  2. 293
      node_tcp.cc
  3. 71
      spec/index.html
  4. 50
      tcp_example.js

3
node.cc

@ -154,7 +154,7 @@ main (int argc, char *argv[])
g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction()); g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction());
Init_timer(g); Init_timer(g);
//Init_tcp(g); Init_tcp(g);
Init_http(g); Init_http(g);
V8::SetFatalErrorHandler(OnFatalError); V8::SetFatalErrorHandler(OnFatalError);
@ -169,3 +169,4 @@ main (int argc, char *argv[])
return exit_code; return exit_code;
} }

293
node_tcp.cc

@ -2,7 +2,7 @@
#include "node.h" #include "node.h"
#include <oi_socket.h> #include <oi_socket.h>
#include <oi_async.h> #include <oi_buf.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -10,36 +10,42 @@
using namespace v8; using namespace v8;
static Persistent<String> readyState_str;
/* static Persistent<Integer> readyState_CONNECTING;
Target API 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", class TCPClient {
public:
port: 80, TCPClient(Handle<Object> obj);
~TCPClient();
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>");
}, 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 = static struct addrinfo tcp_hints =
/* ai_flags */ { AI_PASSIVE /* ai_flags */ { AI_PASSIVE
@ -52,130 +58,203 @@ static struct addrinfo tcp_hints =
/* ai_next */ , 0 /* ai_next */ , 0
}; };
class TCPClient { static Handle<Value> newTCPClient
public: ( const Arguments& args
oi_task resolve_task;
oi_socket socket;
struct addrinfo *address;
Persistent<Object> options;
};
static void on_connect
( oi_socket *socket
) )
{ {
TCPClient *client = static_cast<TCPClient*> (socket->data); if (args.Length() < 1)
return Undefined();
HandleScope scope; HandleScope scope;
Handle<Value> connect_value = client->options->Get( String::NewSymbol("connect") ); String::AsciiValue host(args[0]);
if (!connect_value->IsFunction()) String::AsciiValue port(args[1]);
return; // error!
Handle<Function> connect_cb = Handle<Function>::Cast(connect_value);
TryCatch try_catch; TCPClient *client = new TCPClient(args.This());
Handle<Value> r = connect_cb->Call(client->options, 0, NULL); if(client == NULL)
if (r.IsEmpty()) { return Undefined(); // XXX raise error?
String::Utf8Value error(try_catch.Exception());
printf("connect error: %s\n", *error); int r = client->Connect(*host, *port);
} if (r != 0)
return Undefined(); // XXX raise error?
return args.This();
} }
static void resolve_done static TCPClient*
( oi_task *resolve_task UnwrapClient (Handle<Object> obj)
, int result
)
{ {
TCPClient *client = static_cast<TCPClient*> (resolve_task->data); HandleScope scope;
Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
printf("hello world\n"); TCPClient* client = static_cast<TCPClient*>(field->Value());
return client;
}
if(result != 0) { static Handle<Value>
printf("error. TODO make call options error callback\n"); WriteCallback (const Arguments& args)
client->options.Dispose(); {
delete client; HandleScope scope;
return; 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; TCPClient::TCPClient(Handle<Object> _js_client)
client->socket.on_read = NULL; {
client->socket.on_drain = NULL; oi_socket_init(&socket, 30.0); // TODO adjustable timeout
client->socket.on_error = NULL; socket.on_connect = on_connect;
client->socket.on_close = NULL; socket.on_read = NULL;
client->socket.on_timeout = NULL; socket.on_drain = NULL;
client->socket.data = client; socket.on_error = NULL;
socket.on_close = NULL;
socket.on_timeout = NULL;
socket.data = this;
oi_socket_connect (&client->socket, client->address); HandleScope scope;
oi_socket_attach (&client->socket, node_loop()); js_client = Persistent<Object>::New(_js_client);
js_client->SetInternalField (0, External::New(this));
js_client.MakeWeak (this, client_destroy);
}
freeaddrinfo(client->address); TCPClient::~TCPClient ()
client->address = NULL; {
Disconnect();
oi_socket_detach (&socket);
js_client.Dispose();
js_client.Clear(); // necessary?
} }
static Handle<Value> Connect int
( const Arguments& args TCPClient::Connect(char *host, char *port)
)
{ {
if (args.Length() < 1) int r;
return Undefined();
HandleScope scope; HandleScope scope;
Handle<Value> arg = args[0]; js_client->Set(readyState_str, readyState_CONNECTING);
Handle<Object> options = arg->ToObject();
/* 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()) freeaddrinfo(address);
return False(); address = NULL;
}
Handle<Value> port_value = options->Get( String::NewSymbol("port") ); void TCPClient::Write (Handle<Value> arg)
{
HandleScope scope;
if(port_value->IsUndefined()) if(arg == Null()) {
return False();
Handle<String> host = host_value->ToString(); oi_socket_write_eof(&socket);
Handle<String> port = port_value->ToString();
char host_s[host->Length()+1]; // + 1 for \0 } else {
char port_s[port->Length()+1]; Local<String> s = arg->ToString();
host->WriteAscii(host_s); oi_buf *buf = oi_buf_new2(s->Length());
port->WriteAscii(port_s); s->WriteAscii(buf->base, 0, s->Length());
printf("resolving host: %s, port: %s\n", host_s, port_s); oi_socket_write(&socket, buf);
}
}
TCPClient *client = new TCPClient; void
TCPClient::Disconnect()
{
oi_socket_close(&socket);
}
oi_task_init_getaddrinfo ( &client->resolve_task void TCPClient::OnOpen()
, resolve_done {
, host_s HandleScope scope;
, port_s
, &tcp_hints
, &client->address
);
client->options = Persistent<Object>::New(options);
oi_async_submit (&thread_pool, &client->resolve_task); js_client->Set(readyState_str, readyState_OPEN);
Handle<Value> onopen_value = js_client->Get( String::NewSymbol("onopen") );
if (!onopen_value->IsFunction())
return;
Handle<Function> onopen = Handle<Function>::Cast(onopen_value);
TryCatch try_catch;
Handle<Value> r = onopen->Call(js_client, 0, NULL);
if(try_catch.HasCaught())
node_fatal_exception(try_catch);
} }
void void
Init_tcp (Handle<Object> target) Init_tcp (Handle<Object> target)
{ {
oi_async_init(&thread_pool);
oi_async_attach(node_loop(), &thread_pool);
HandleScope scope; 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
);
Local<Object> tcp = Object::New(); readyState_OPEN = Persistent<Integer>::New(Integer::New(READY_STATE_OPEN));
target->Set(String::NewSymbol("TCP"), tcp); 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);
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());
} }

71
spec/index.html

@ -64,22 +64,33 @@
<p>Unless otherwise noted, all functions can be considered <p>Unless otherwise noted, all functions can be considered
non-blocking. Non-blocking means that program execution will continue 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> <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> <h3 id=execution-context><span class=secno>1.2 </span>Execution context</h3>
<p>... <p>Global data is shared between callbacks.
<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=http_server><span class=secno>2 </span>HTTP Server</h2>
<h2 id=tcp_client><span class=secno>3 </span>TCP Client</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#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 // ready state
const unsigned short CONNECTING = 0; const unsigned short CONNECTING = 0;
@ -89,16 +100,60 @@
// networking // networking
attribute Function onopen; attribute Function onopen;
attribute Function onrecv; attribute Function onread;
attribute Function onclose; attribute Function onclose;
void send(in DOMString data); void write(in DOMString data);
void disconnect(); void disconnect();
};</pre> };</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> <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 There are four globally exposed functions
<code>setTimeout</code>, <code>setTimeout</code>,
<code>clearTimeout</code>, <code>clearTimeout</code>,

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

Loading…
Cancel
Save