}
#toc a { color: #777; }
-h1, h2, h3 { color: #aaf; }
+h1, h2, h3, h4 { color: #bbb; }
h1 {
margin: 2em 0;
h1 a { color: inherit; }
h2 {
- font-size: 45px;
+ font-size: 30px;
line-height: inherit;
font-weight: bold;
+ margin: 2em 0;
}
h3 {
+ margin: 2em 0;
+ font-size: 20px;
+ line-height: inherit;
+ font-weight: bold;
+}
+
+h4 {
margin: 1em 0;
- font-size: 30px;
+ font-size: inherit;
line-height: inherit;
- font-weight: inherit;
+ font-weight: bold;
}
pre, code {
font-family: monospace;
font-size: 13pt;
- color: #eae;
+ color: #aaf;
}
pre {
<h2 id="motivation">Motivation</h2>
-<ol>
-<li>Evented programming makes sense
- <ol>
- <li>difference between blocking/non-blocking design
- <p> There are many methods to write internet servers but they can
- fundamentally be divided into two camps: evented and threaded; non-blocking
- and blocking. A blocking server accepts a connection and launches a new
- thread to handle the connection. Because the concurrency is handled by
- the thread scheduler, a blocking server can make function calls which
- preform full network requests.
+<h3>Evented Programming Makes More Sense</h3>
+
+difference between blocking/non-blocking design
+
+<p> There are many methods to write internet servers but they can
+fundamentally be divided into two camps: evented and threaded; non-blocking
+and blocking. A blocking server accepts a connection and launches a new
+thread to handle the connection. Because the concurrency is handled by
+the thread scheduler, a blocking server can make function calls which
+preform full network requests.
<pre class="sh_javascript">var response = db.execute("SELECT * FROM table");
// do something</pre>
- <p> An evented server manages its concurrency itself. All connections
- are handled in a single thread and callbacks are executed on certain
- events: "socket 23 is has data to read", "socket 65's write buffer is
- empty". An evented server executes small bits of code but never
- <i>blocks</i> the process. In the evented world callbacks are used
- instead of functions
+<p> An evented server manages its concurrency itself. All connections
+are handled in a single thread and callbacks are executed on certain
+events: "socket 23 is has data to read", "socket 65's write buffer is
+empty". An evented server executes small bits of code but never
+<i>blocks</i> the process. In the evented world callbacks are used
+instead of functions
<pre class="sh_javascript">db.execute("SELECT * FROM table", function (response) {
-// do something
+ // do something
});</pre>
- <li><a href="http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait">I/O latency</a>
+
+
+<p><a href="http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait">I/O latency</a>
<pre>
l1 cache ~ 3
l2 cache ~ 14
disk ~ 41000000
network ~ 240000000
</pre>
- <li>purely evented interfaces rule out a lot of stupidity
- </ol>
-<li>Evented programs are more efficient
+
+<p>purely evented interfaces rule out a lot of stupidity
+
+<h3>Evented programs are more efficient</h3>
<ol>
<li>pthread stack size
2mb default stack size on linux (1mb on windows, 64kb on FreeBSD)
<li>Apache vs. Nginx
<li>event machine vs mongrel (neverblock)
</ol>
-<li>The appropriateness of Javascript
+<h3>The appropriateness of Javascript</h3>
<ol>
<li>No I/O
<p> Javascript is without I/O. In the browser the DOM provides I/O,
systems tend to be written in C and portable web-level systems are
written in Javascript.
</ol>
-</ol>
<h2 id="benchmarks">Benchmarks</h2>
if (args[0]->IsFunction() == false)
return ThrowException(String::New("Must pass a class as the first argument."));
Local<Function> protocol_class = Local<Function>::Cast(args[0]);
- new HTTPConnection(args.This(), protocol_class, HTTP_RESPONSE);
+ new HTTPConnection(args.This(), HTTP_RESPONSE);
return args.This();
}
if (args[0]->IsFunction() == false)
return ThrowException(String::New("Must pass a class as the first argument."));
Local<Function> protocol_class = Local<Function>::Cast(args[0]);
- new HTTPConnection(args.This(), protocol_class, HTTP_REQUEST);
+ new HTTPConnection(args.This(), HTTP_REQUEST);
return args.This();
}
HTTPConnection *connection = static_cast<HTTPConnection*> (parser->data);
HandleScope scope;
- Local<Object> protocol = connection->GetProtocol();
- Local<Value> on_message_v = protocol->Get(ON_MESSAGE_SYMBOL);
+ Local<Value> on_message_v = connection->handle_->Get(ON_MESSAGE_SYMBOL);
if (!on_message_v->IsFunction()) return -1;
Handle<Function> on_message = Handle<Function>::Cast(on_message_v);
return 0;
}
-HTTPConnection::HTTPConnection (Handle<Object> handle, Handle<Function> protocol_class, enum http_parser_type type)
- : Connection(handle, protocol_class)
+HTTPConnection::HTTPConnection (Handle<Object> handle, enum http_parser_type type)
+ : Connection(handle)
{
http_parser_init (&parser_, type);
parser_.on_message_begin = on_message_begin;
{
HandleScope scope;
- Local<Function> protocol_class = GetProtocolClass();
- if (protocol_class.IsEmpty()) {
+ Local<Function> connection_handler = GetConnectionHandler ();
+ if (connection_handler.IsEmpty()) {
Close();
return NULL;
}
- Handle<Value> argv[] = { protocol_class };
Local<Object> connection_handle =
- HTTPConnection::server_constructor_template->GetFunction()->NewInstance(1, argv);
+ HTTPConnection::server_constructor_template->GetFunction()->NewInstance(0, NULL);
HTTPConnection *connection = NODE_UNWRAP(HTTPConnection, connection_handle);
connection->SetAcceptor(handle_);
static v8::Handle<v8::Value> v8NewServer (const v8::Arguments& args);
HTTPConnection (v8::Handle<v8::Object> handle,
- v8::Handle<v8::Function> protocol_class,
enum http_parser_type type);
void OnReceive (const void *buf, size_t len);
using namespace v8;
using namespace node;
+#define UTF8_SYMBOL String::NewSymbol("utf8")
+#define RAW_SYMBOL String::NewSymbol("raw")
+
#define ON_RECEIVE_SYMBOL String::NewSymbol("onReceive")
#define ON_DISCONNECT_SYMBOL String::NewSymbol("onDisconnect")
#define ON_CONNECT_SYMBOL String::NewSymbol("onConnect")
#define SERVER_SYMBOL String::NewSymbol("server")
#define PROTOCOL_SYMBOL String::NewSymbol("protocol")
-#define PROTOCOL_CLASS_SYMBOL String::NewSymbol("protocol_class")
+#define CONNECTION_HANDLER_SYMBOL String::NewSymbol("connection_handler")
static const struct addrinfo tcp_hints =
/* ai_flags */ { AI_PASSIVE
NODE_SET_PROTOTYPE_METHOD(constructor_template, "fullClose", v8FullClose);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "forceClose", v8ForceClose);
+ constructor_template->PrototypeTemplate()->SetAccessor(
+ String::NewSymbol("encoding"),
+ EncodingGetter,
+ EncodingSetter);
+
target->Set(String::NewSymbol("Connection"), constructor_template->GetFunction());
}
-Connection::Connection (Handle<Object> handle, Handle<Function> protocol_class)
- : ObjectWrap(handle)
+Handle<Value>
+Connection::EncodingGetter (Local<String> _, const AccessorInfo& info)
{
+ Connection *connection = NODE_UNWRAP(Connection, info.This());
HandleScope scope;
- // Instanciate the protocol object
- Handle<Value> argv[] = { handle_ };
- Local<Object> protocol = protocol_class->NewInstance(1, argv);
- handle_->Set(PROTOCOL_SYMBOL, protocol);
-
- // TODO use SetNamedPropertyHandler (or whatever) for encoding and timeout
- // instead of just reading it once?
+ if (connection->encoding_ == UTF8)
+ return scope.Close(UTF8_SYMBOL);
+ else
+ return scope.Close(RAW_SYMBOL);
+}
- encoding_ = RAW;
- Local<Value> encoding_v = protocol->Get(ENCODING_SYMBOL);
- if (encoding_v->IsString()) {
- Local<String> encoding_string = encoding_v->ToString();
- char buf[5]; // need enough room for "utf8" or "raw"
- encoding_string->WriteAscii(buf, 0, 4);
- buf[4] = '\0';
- if(strcasecmp(buf, "utf8") == 0) encoding_ = UTF8;
+void
+Connection::EncodingSetter (Local<String> _, Local<Value> value, const AccessorInfo& info)
+{
+ Connection *connection = NODE_UNWRAP(Connection, info.This());
+ if (!value->IsString()) {
+ connection->encoding_ = RAW;
+ return;
}
+ HandleScope scope;
+ Local<String> encoding = value->ToString();
+ char buf[5]; // need enough room for "utf8" or "raw"
+ encoding->WriteAscii(buf, 0, 4);
+ buf[4] = '\0';
+ if(strcasecmp(buf, "utf8") == 0)
+ connection->encoding_ = UTF8;
+ else
+ connection->encoding_ = RAW;
+}
- double timeout = 0.0; // default
- Local<Value> timeout_v = protocol->Get(TIMEOUT_SYMBOL);
- if (encoding_v->IsInt32())
- timeout = timeout_v->Int32Value() / 1000.0;
+Connection::Connection (Handle<Object> handle)
+ : ObjectWrap(handle)
+{
+ encoding_ = RAW;
+ double timeout = 60.0; // default
host_ = NULL;
port_ = NULL;
ForceClose();
}
-Local<Object>
-Connection::GetProtocol (void)
-{
- HandleScope scope;
-
- Local<Value> protocol_v = handle_->Get(PROTOCOL_SYMBOL);
- if (protocol_v->IsObject()) {
- Local<Object> protocol = protocol_v->ToObject();
- return scope.Close(protocol);
- }
-
- return Local<Object>();
-}
-
void
Connection::SetAcceptor (Handle<Object> acceptor_handle)
{
Connection::v8New (const Arguments& args)
{
HandleScope scope;
- if (args[0]->IsFunction() == false)
- return ThrowException(String::New("Must pass a class as the first argument."));
- Handle<Function> protocol_class = Handle<Function>::Cast(args[0]);
- new Connection(args.This(), protocol_class);
+ new Connection(args.This());
return args.This();
}
{
HandleScope scope;
- Local<Object> protocol = GetProtocol();
- Handle<Value> callback_v = protocol->Get(ON_RECEIVE_SYMBOL);
+ Handle<Value> callback_v = handle_->Get(ON_RECEIVE_SYMBOL);
if (!callback_v->IsFunction()) return;
Handle<Function> callback = Handle<Function>::Cast(callback_v);
}
TryCatch try_catch;
- callback->Call(protocol, argc, argv);
+ callback->Call(handle_, argc, argv);
if (try_catch.HasCaught())
fatal_exception(try_catch); // XXX is this the right action to take?
void name () \
{ \
HandleScope scope; \
- Local<Object> protocol = GetProtocol(); \
- Local<Value> callback_v = protocol->Get(symbol); \
+ Local<Value> callback_v = handle_->Get(symbol); \
if (!callback_v->IsFunction()) return; \
Handle<Function> callback = Handle<Function>::Cast(callback_v); \
- callback->Call(protocol, 0, NULL); \
+ callback->Call(handle_, 0, NULL); \
}
DEFINE_SIMPLE_CALLBACK(Connection::OnConnect, ON_CONNECT_SYMBOL)
target->Set(String::NewSymbol("Server"), constructor_template->GetFunction());
}
-Acceptor::Acceptor (Handle<Object> handle, Handle<Function> protocol_class, Handle<Object> options)
+Acceptor::Acceptor (Handle<Object> handle, Handle<Function> connection_handler, Handle<Object> options)
: ObjectWrap(handle)
{
HandleScope scope;
- handle_->SetHiddenValue(PROTOCOL_CLASS_SYMBOL, protocol_class);
+ handle_->SetHiddenValue(CONNECTION_HANDLER_SYMBOL, connection_handler);
int backlog = 1024; // default value
Local<Value> backlog_v = options->Get(String::NewSymbol("backlog"));
{
HandleScope scope;
- Local<Function> protocol_class = GetProtocolClass();
- if (protocol_class.IsEmpty()) {
- printf("protocol class was empty!");
+ Local<Function> connection_handler = GetConnectionHandler();
+ if (connection_handler.IsEmpty()) {
+ printf("Connection handler was empty!");
Close();
return NULL;
}
- Handle<Value> argv[] = { protocol_class };
Local<Object> connection_handle =
- Connection::constructor_template->GetFunction()->NewInstance(1, argv);
+ Connection::constructor_template->GetFunction()->NewInstance(0, NULL);
Connection *connection = NODE_UNWRAP(Connection, connection_handle);
connection->SetAcceptor(handle_);
+ Handle<Value> argv[1] = { connection_handle };
+
+ TryCatch try_catch;
+ Local<Value> ret = connection_handler->Call(handle_, 1, argv);
+
+ if (ret.IsEmpty())
+ fatal_exception(try_catch);
+
return connection;
}
if (args.Length() < 1 || args[0]->IsFunction() == false)
return ThrowException(String::New("Must at give connection handler as the first argument"));
- Local<Function> protocol_class = Local<Function>::Cast(args[0]);
+ Local<Function> connection_handler = Local<Function>::Cast(args[0]);
Local<Object> options;
if (args.Length() > 1 && args[1]->IsObject()) {
options = Object::New();
}
- new Acceptor(args.This(), protocol_class, options);
+ new Acceptor(args.This(), connection_handler, options);
return args.This();
}
}
Local<v8::Function>
-Acceptor::GetProtocolClass (void)
+Acceptor::GetConnectionHandler (void)
{
HandleScope scope;
- Local<Value> protocol_class_v = handle_->GetHiddenValue(PROTOCOL_CLASS_SYMBOL);
- if (protocol_class_v->IsFunction()) {
- Local<Function> protocol_class = Local<Function>::Cast(protocol_class_v);
- return scope.Close(protocol_class);
+ Local<Value> connection_handler_v = handle_->GetHiddenValue(CONNECTION_HANDLER_SYMBOL);
+ if (connection_handler_v->IsFunction()) {
+ Local<Function> connection_handler = Local<Function>::Cast(connection_handler_v);
+ return scope.Close(connection_handler);
}
return Local<Function>();
}
+
static v8::Handle<v8::Value> v8Close (const v8::Arguments& args);
static v8::Handle<v8::Value> v8FullClose (const v8::Arguments& args);
static v8::Handle<v8::Value> v8ForceClose (const v8::Arguments& args);
+ static v8::Handle<v8::Value> EncodingGetter (v8::Local<v8::String> _, const v8::AccessorInfo& info);
+ static void EncodingSetter (v8::Local<v8::String> _, v8::Local<v8::Value> value, const v8::AccessorInfo& info);
- Connection (v8::Handle<v8::Object> handle, v8::Handle<v8::Function> protocol_class);
+ Connection (v8::Handle<v8::Object> handle);
virtual ~Connection ();
int Connect (struct addrinfo *address) { return oi_socket_connect (&socket_, address); }
static v8::Handle<v8::Value> v8Close (const v8::Arguments& args);
Acceptor (v8::Handle<v8::Object> handle,
- v8::Handle<v8::Function> protocol_class,
+ v8::Handle<v8::Function> connection_handler,
v8::Handle<v8::Object> options);
virtual ~Acceptor () { Close(); puts("acceptor gc'd!");}
- v8::Local<v8::Function> GetProtocolClass (void);
+ v8::Local<v8::Function> GetConnectionHandler (void);
int Listen (struct addrinfo *address) {
int r = oi_server_listen (&server_, address);
var count = 0;
function Ponger (socket) {
- this.encoding = "UTF8";
- this.timeout = 0;
+ socket.encoding = "UTF8";
+ socket.timeout = 0;
- this.onConnect = function () {
- puts("got socket.");
- };
+ puts("got socket.");
- this.onReceive = function (data) {
+ socket.onReceive = function (data) {
+ //puts("server recved data: " + JSON.stringify(data));
assertTrue(count <= N);
stdout.print("-");
if (/PING/.exec(data)) {
}
};
- this.onEOF = function () {
+ socket.onEOF = function () {
puts("ponger: onEOF");
socket.close();
};
- this.onDisconnect = function () {
+ socket.onDisconnect = function () {
puts("ponger: onDisconnect");
socket.server.close();
};
}
-function Pinger (socket) {
- this.encoding = "UTF8";
+function onLoad() {
+ var server = new node.tcp.Server(Ponger);
+ server.listen(port);
+
+ var client = new node.tcp.Connection();
+
+ client.encoding = "UTF8";
- this.onConnect = function () {
- socket.send("PING");
+ client.onConnect = function () {
+ puts("client is connected.");
+ client.send("PING");
};
- this.onReceive = function (data) {
+ client.onReceive = function (data) {
+ //puts("client recved data: " + JSON.stringify(data));
stdout.print(".");
assertEquals("PONG", data);
count += 1;
if (count < N) {
- socket.send("PING");
+ client.send("PING");
} else {
puts("sending FIN");
- socket.close();
+ client.close();
}
};
- this.onEOF = function () {
+ client.onEOF = function () {
puts("pinger: onEOF");
assertEquals(N, count);
};
-}
-
-function onLoad() {
- var server = new node.tcp.Server(Ponger);
- server.listen(port);
- var client = new node.tcp.Connection(Pinger);
client.connect(port);
}