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

293
node_tcp.cc

@ -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>
@ -10,36 +10,42 @@
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);
/* 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;
}
r = oi_socket_connect (&socket, address);
if(r != 0) {
perror("oi_socket_connect");
return r;
}
oi_socket_attach (&socket, node_loop());
freeaddrinfo(address);
address = NULL;
}
void TCPClient::Write (Handle<Value> arg)
{
HandleScope scope;
/* Make sure the user has provided at least host and port */
if(arg == Null()) {
Handle<Value> host_value = options->Get( String::NewSymbol("host") );
oi_socket_write_eof(&socket);
if(host_value->IsUndefined())
return False();
} else {
Local<String> s = arg->ToString();
Handle<Value> port_value = options->Get( String::NewSymbol("port") );
oi_buf *buf = oi_buf_new2(s->Length());
s->WriteAscii(buf->base, 0, s->Length());
if(port_value->IsUndefined())
return False();
oi_socket_write(&socket, buf);
}
}
Handle<String> host = host_value->ToString();
Handle<String> port = port_value->ToString();
void
TCPClient::Disconnect()
{
oi_socket_close(&socket);
}
char host_s[host->Length()+1]; // + 1 for \0
char port_s[port->Length()+1];
void TCPClient::OnOpen()
{
HandleScope scope;
host->WriteAscii(host_s);
port->WriteAscii(port_s);
js_client->Set(readyState_str, readyState_OPEN);
printf("resolving host: %s, port: %s\n", host_s, port_s);
Handle<Value> onopen_value = js_client->Get( String::NewSymbol("onopen") );
if (!onopen_value->IsFunction())
return;
Handle<Function> onopen = Handle<Function>::Cast(onopen_value);
TCPClient *client = new TCPClient;
TryCatch try_catch;
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> r = onopen->Call(js_client, 0, NULL);
oi_async_submit (&thread_pool, &client->resolve_task);
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());
}

71
spec/index.html

@ -64,22 +64,33 @@
<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>...
<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=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;
@ -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();
};</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>,

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