Major refactoring: program name now "node"
authorRyan <ry@tinyclouds.org>
Tue, 3 Mar 2009 00:56:15 +0000 (01:56 +0100)
committerRyan <ry@tinyclouds.org>
Tue, 3 Mar 2009 00:56:15 +0000 (01:56 +0100)
Trying to make a more moduler design. Two libraries currently "TCP" and
"HTTP" each have their own file.

Other major feature added here is multiple web servers! excitement.

Makefile
http_api.js [new file with mode: 0644]
node.cc [new file with mode: 0644]
node_http.cc [moved from server.cc with 66% similarity]
node_http.h [new file with mode: 0644]
node_tcp.cc [moved from tcp.cc with 94% similarity]
node_tcp.h [new file with mode: 0644]
tcp.h [deleted file]

index 6d7086c..5b6082f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 EVDIR=$(HOME)/local/libev
 V8INC = $(HOME)/src/v8/include
-V8LIB = $(HOME)/src/v8/libv8.a
+V8LIB = $(HOME)/src/v8/libv8_g.a
 
 CFLAGS = -g -I$(V8INC) -Ideps/oi -DHAVE_GNUTLS=0 -Ideps/ebb 
 LDFLAGS = -lev -pthread # -lefence
@@ -10,20 +10,17 @@ ifdef EVDIR
        LDFLAGS += -L$(EVDIR)/lib
 endif
 
-server: server.o tcp.o oi_socket.o oi_async.o ebb_request_parser.o oi_buf.o
-       g++ -o server $^ $(LDFLAGS) $(V8LIB) 
+node: node.o node_tcp.o node_http.o oi_socket.o oi_async.o oi_buf.o ebb_request_parser.o
+       g++ -o node $^ $(LDFLAGS) $(V8LIB) 
 
-server.o: server.cc 
+node.o: node.cc 
        g++ $(CFLAGS) -c $<
 
-tcp.o: tcp.cc 
-       g++ $(CFLAGS) -c $<
-       
-ebb_request_parser.o: ebb_request_parser.c deps/ebb/ebb_request_parser.h 
+node_tcp.o: node_tcp.cc 
        g++ $(CFLAGS) -c $<
 
-ebb_request_parser.c: deps/ebb/ebb_request_parser.rl
-       ragel -s -G2 $< -o $@
+node_http.o: node_http.cc 
+       g++ $(CFLAGS) -c $<
 
 oi_socket.o: deps/oi/oi_socket.c deps/oi/oi_socket.h 
        gcc $(CFLAGS) -c $<
@@ -33,10 +30,16 @@ oi_async.o: deps/oi/oi_async.c deps/oi/oi_async.h
 
 oi_buf.o: deps/oi/oi_buf.c deps/oi/oi_buf.h 
        gcc $(CFLAGS) -c $<
+       
+ebb_request_parser.o: ebb_request_parser.c deps/ebb/ebb_request_parser.h 
+       g++ $(CFLAGS) -c $<
+
+ebb_request_parser.c: deps/ebb/ebb_request_parser.rl
+       ragel -s -G2 $< -o $@
 
 clean:
        rm -f ebb_request_parser.c
        rm -f *.o 
-       rm -f server
+       rm -f node
 
 .PHONY: clean test
diff --git a/http_api.js b/http_api.js
new file mode 100644 (file)
index 0000000..9fb8086
--- /dev/null
@@ -0,0 +1,30 @@
+function encode(data) { 
+  var chunk = data.toString();
+  return chunk.length.toString(16) + "\r\n" + chunk + "\r\n";
+}
+
+var server = new HTTP.Server("localhost", 8000);
+
+server.onRequest = function (request) {
+  log( "path: " + request.path );
+  log( "query string: " + request.query_string );
+
+  // onBody sends null on the last chunk.
+  request.onBody = function (chunk) {
+    if(chunk) { 
+      this.respond(encode(chunk));
+    } else {
+      this.respond(encode("\n"));
+      this.respond("0\r\n\r\n");
+      this.respond(null); // signals end-of-request
+    }
+  }
+  request.respond("HTTP/1.0 200 OK\r\n");
+  request.respond("Content-Type: text/plain\r\n");
+  request.respond("Transfer-Encoding: chunked\r\n");
+  request.respond("\r\n");
+
+};
+/*
+server.close();
+*/
diff --git a/node.cc b/node.cc
new file mode 100644 (file)
index 0000000..13d40ca
--- /dev/null
+++ b/node.cc
@@ -0,0 +1,154 @@
+#include <oi.h>
+
+#include "node_tcp.h"
+#include "node_http.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <string>
+#include <list>
+#include <map>
+
+#include <v8.h>
+
+using namespace v8;
+using namespace std;
+
+static struct ev_loop *loop;
+
+static Persistent<Context> context;
+static Persistent<Function> process_;
+
+// Reads a file into a v8 string.
+static Handle<String> ReadFile
+  ( const string& name
+  ) 
+{
+  FILE* file = fopen(name.c_str(), "rb");
+  if (file == NULL) return Handle<String>();
+
+  fseek(file, 0, SEEK_END);
+  int size = ftell(file);
+  rewind(file);
+
+  char* chars = new char[size + 1];
+  chars[size] = '\0';
+  for (int i = 0; i < size;) {
+    int read = fread(&chars[i], 1, size - i, file);
+    i += read;
+  }
+  fclose(file);
+  Handle<String> result = String::New(chars, size);
+  delete[] chars;
+  return result;
+}
+
+static void ParseOptions
+  ( int argc
+  , char* argv[]
+  , map<string, string>& options
+  , string* file
+  )
+{
+  for (int i = 1; i < argc; i++) {
+    string arg = argv[i];
+    int index = arg.find('=', 0);
+    if (index == string::npos) {
+      *file = arg;
+    } else {
+      string key = arg.substr(0, index);
+      string value = arg.substr(index+1);
+      options[key] = value;
+    }
+  }
+}
+
+static bool compile
+  ( Handle<String> script
+  ) 
+{
+  HandleScope handle_scope;
+
+  // We're just about to compile the script; set up an error handler to
+  // catch any exceptions the script might throw.
+  TryCatch try_catch;
+
+  // Compile the script and check for errors.
+  Handle<Script> compiled_script = Script::Compile(script);
+  if (compiled_script.IsEmpty()) {
+
+    Handle<Message> message = try_catch.Message();
+
+    String::Utf8Value error(try_catch.Exception());
+
+    printf("error: %s line %d\n", *error, message->GetLineNumber());
+
+    return false;
+  }
+
+  // Run the script!
+  Handle<Value> result = compiled_script->Run();
+  if (result.IsEmpty()) {
+    // The TryCatch above is still in effect and will have caught the error.
+    String::Utf8Value error(try_catch.Exception());
+    printf("error: %s\n", *error);
+    // Running the script failed; bail out.
+    return false;
+  }
+  return true;
+}
+
+static Handle<Value> LogCallback
+  ( const Arguments& args
+  ) 
+{
+  if (args.Length() < 1) return v8::Undefined();
+  HandleScope scope;
+  Handle<Value> arg = args[0];
+  String::Utf8Value value(arg);
+
+  printf("Logged: %s\n", *value);
+  fflush(stdout);
+
+  return v8::Undefined();
+}
+
+int main 
+  ( int argc
+  , char *argv[]
+  ) 
+{
+  loop = ev_default_loop(0);
+
+  map<string, string> options;
+  string file;
+  ParseOptions(argc, argv, options, &file);
+  if (file.empty()) {
+    fprintf(stderr, "No script was specified.\n");
+    return 1;
+  }
+  HandleScope scope;
+  Handle<String> source = ReadFile(file);
+  if (source.IsEmpty()) {
+    fprintf(stderr, "Error reading '%s'.\n", file.c_str());
+    return 1;
+  }
+
+  context = Context::New(NULL, ObjectTemplate::New());
+  Context::Scope context_scope(context);
+
+  Local<Object> g = Context::GetCurrent()->Global();
+  g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction());
+  g->Set( String::New("TCP"), node_tcp_initialize(loop));
+  g->Set( String::New("HTTP"), node_http_initialize(loop));
+
+  // Compile and run the script
+  if (!compile(source))
+    return false;
+
+  ev_loop(loop, 0);
+
+  context.Dispose();
+
+  return 0;
+}
similarity index 66%
rename from server.cc
rename to node_http.cc
index def6287..aaf85eb 100644 (file)
--- a/server.cc
@@ -1,37 +1,51 @@
-#include <oi.h>
-#include <ebb_request_parser.h>
+#include "node_http.h"
 
-#include "tcp.h"
+#include <oi_socket.h>
+#include <ebb_request_parser.h>
 
-#include <stdio.h>
-#include <assert.h>
 #include <string>
 #include <list>
-#include <map>
 
 #include <v8.h>
 
 using namespace v8;
 using namespace std;
 
-#define PORT "1981"
-
-static oi_server server;
+static Persistent<ObjectTemplate> request_template;
 static struct ev_loop *loop;
 
-static Persistent<Context> context;
-static Persistent<Function> process_;
-static Persistent<ObjectTemplate> request_template_;
+class Server {
+public:
+  Server (Handle<Object> _js_server);
+  ~Server ();
+
+  int Start(struct addrinfo *servinfo);
+  void Stop();
+
+  Handle<Value> Callback()
+  {
+    HandleScope scope;
+    Handle<Value> value = js_server->Get(String::New("onRequest"));
+    return scope.Close(value);
+  }
+
+private:
+  oi_server server;
+  Persistent<Object> js_server;
+};
 
 class Connection {
 public:
-  Connection ( void)
+  Connection ()
   {
     oi_socket_init (&socket, 30.0);
     ebb_request_parser_init (&parser);
   }
   ebb_request_parser parser;
   oi_socket socket;
+  Persistent<Function> js_onRequest;
+
+  friend class Server;
 };
 
 class HttpRequest {
@@ -39,6 +53,8 @@ class HttpRequest {
   HttpRequest (Connection &c);
   ~HttpRequest();
 
+  void MakeBodyCallback (const char *base, size_t length);
+
   string path;
   string query_string;
   string fragment;
@@ -62,17 +78,14 @@ static HttpRequest* UnwrapRequest
   return static_cast<HttpRequest*>(ptr);
 }
 
-static void make_onBody_callback
-  ( HttpRequest *request
-  , const char *base
-  , size_t length
-  )
+void
+HttpRequest::MakeBodyCallback (const char *base, size_t length)
 {
   HandleScope handle_scope;
-
-  Handle<Object> obj = request->js_object;
+  // 
   // XXX don't always allocate onBody strings
-  Handle<Value> onBody_val = request->js_object->Get(String::NewSymbol("onBody"));  
+  //
+  Handle<Value> onBody_val = js_object->Get(String::NewSymbol("onBody"));  
   if (!onBody_val->IsFunction()) return;
   Handle<Function> onBody = Handle<Function>::Cast(onBody_val);
 
@@ -81,13 +94,14 @@ static void make_onBody_callback
   Handle<Value> argv[argc];
   
   if(length) {
+    // TODO use array for binary data
     Handle<String> chunk = String::New(base, length);
     argv[0] = chunk;
   } else {
     argv[0] = Null();
   }
 
-  Handle<Value> result = onBody->Call(request->js_object, argc, argv);
+  Handle<Value> result = onBody->Call(js_object, argc, argv);
 
   if (result.IsEmpty()) {
     String::Utf8Value error(try_catch.Exception());
@@ -250,17 +264,16 @@ static void on_headers_complete
 
   HandleScope handle_scope;
 
-  if (request_template_.IsEmpty()) {
+  if (request_template.IsEmpty()) {
     Handle<ObjectTemplate> raw_template = ObjectTemplate::New();
     raw_template->SetInternalFieldCount(1);
-    raw_template->Set(String::New("respond"), FunctionTemplate::New(RespondCallback));
+    raw_template->Set(String::NewSymbol("respond"), FunctionTemplate::New(RespondCallback));
 
-    request_template_ = Persistent<ObjectTemplate>::New(raw_template);
+    request_template = Persistent<ObjectTemplate>::New(raw_template);
   }
-  Handle<ObjectTemplate> templ = request_template_;
 
   // Create an empty http request wrapper.
-  Handle<Object> result = templ->NewInstance();
+  Handle<Object> result = request_template->NewInstance();
 
   // Wrap the raw C++ pointer in an External so it can be referenced
   // from within JavaScript.
@@ -320,9 +333,6 @@ static void on_headers_complete
 
   request->js_object = Persistent<Object>::New(result);
 
-  // Enter this processor's context so all the remaining operations
-  // take place there
-  Context::Scope context_scope(context);
 
   // Set up an exception handler before calling the Process function
   TryCatch try_catch;
@@ -331,7 +341,7 @@ static void on_headers_complete
   // and one argument, the request.
   const int argc = 1;
   Handle<Value> argv[argc] = { request->js_object };
-  Handle<Value> r = process_->Call(context->Global(), argc, argv);
+  Handle<Value> r = request->connection.js_onRequest->Call(Context::GetCurrent()->Global(), argc, argv);
   if (r.IsEmpty()) {
     String::Utf8Value error(try_catch.Exception());
     printf("error: %s\n", *error);
@@ -343,23 +353,19 @@ static void on_request_complete
   )
 {
   HttpRequest *request = static_cast<HttpRequest*> (req->data);
-
-  make_onBody_callback(request, NULL, 0); // EOF
+  request->MakeBodyCallback(NULL, 0); // EOF
 }
 
-
 static void on_body
   ( ebb_request *req
   , const char *base
   , size_t length
   )
 {
-  //printf("on body %d\n", length);
-
   HttpRequest *request = static_cast<HttpRequest*> (req->data);
 
   if(length)
-    make_onBody_callback(request, base, length);
+    request->MakeBodyCallback(base, length);
 }
 
 static ebb_request * on_request
@@ -419,12 +425,21 @@ static void on_drain
   //oi_socket_close(&connection->socket);
 }
 
-static oi_socket* new_connection 
-  ( oi_server *server
+static oi_socket* on_connection 
+  ( oi_server *_server
   , struct sockaddr *addr
   , socklen_t len
   )
 {
+  HandleScope scope;
+
+  Server *server = static_cast<Server*> (_server->data);
+
+  Handle<Value> callback_v = server->Callback();
+
+  if(callback_v == Undefined())
+    return NULL;
+
   Connection *connection = new Connection();
     connection->socket.on_read    = on_read;
     connection->socket.on_error   = NULL;
@@ -436,159 +451,67 @@ static oi_socket* new_connection
     connection->parser.new_request = on_request;
     connection->parser.data        = connection;
 
+  Handle<Function> f = Handle<Function>::Cast(callback_v);
+  connection->js_onRequest = Persistent<Function>::New(f);
 
   return &connection->socket;
 }
 
-// Reads a file into a v8 string.
-static Handle<String> ReadFile
-  ( const string& name
-  ) 
+static void server_destroy
+  ( Persistent<Value> _
+  , void *data
+  )
 {
-  FILE* file = fopen(name.c_str(), "rb");
-  if (file == NULL) return Handle<String>();
-
-  fseek(file, 0, SEEK_END);
-  int size = ftell(file);
-  rewind(file);
-
-  char* chars = new char[size + 1];
-  chars[size] = '\0';
-  for (int i = 0; i < size;) {
-    int read = fread(&chars[i], 1, size - i, file);
-    i += read;
-  }
-  fclose(file);
-  Handle<String> result = String::New(chars, size);
-  delete[] chars;
-  return result;
+  Server *server = static_cast<Server *> (data);
+  delete server;
 }
 
-static void ParseOptions
-  ( int argc
-  , char* argv[]
-  , map<string, string>& options
-  , string* file
-  )
+Server::Server (Handle<Object> _js_server)
 {
-  for (int i = 1; i < argc; i++) {
-    string arg = argv[i];
-    int index = arg.find('=', 0);
-    if (index == string::npos) {
-      *file = arg;
-    } else {
-      string key = arg.substr(0, index);
-      string value = arg.substr(index+1);
-      options[key] = value;
-    }
-  }
+  oi_server_init(&server, 1024);
+  server.on_connection = on_connection;
+  server.data = this;
+  HandleScope scope;
+  js_server = Persistent<Object>::New (_js_server);
+  // are we ever going to need this external?
+  js_server->SetInternalField (0, External::New(this));
+  js_server.MakeWeak (this, server_destroy);
 }
 
-static bool compile
-  ( Handle<String> script
-  ) 
+Server::~Server ()
 {
-  HandleScope handle_scope;
-
-  // We're just about to compile the script; set up an error handler to
-  // catch any exceptions the script might throw.
-  TryCatch try_catch;
-
-  // Compile the script and check for errors.
-  Handle<Script> compiled_script = Script::Compile(script);
-  if (compiled_script.IsEmpty()) {
-
-    Handle<Message> message = try_catch.Message();
-
-    String::Utf8Value error(try_catch.Exception());
-
-    printf("error: %s line %d\n", *error, message->GetLineNumber());
-
-    return false;
-  }
-
-  // Run the script!
-  Handle<Value> result = compiled_script->Run();
-  if (result.IsEmpty()) {
-    // The TryCatch above is still in effect and will have caught the error.
-    String::Utf8Value error(try_catch.Exception());
-    printf("error: %s\n", *error);
-    // Running the script failed; bail out.
-    return false;
-  }
-  return true;
+  Stop();
+  js_server.Dispose();
+  js_server.Clear(); // necessary? 
 }
 
-static Handle<Value> LogCallback
-  ( const Arguments& args
-  ) 
+int
+Server::Start(struct addrinfo *servinfo) 
 {
-  if (args.Length() < 1) return v8::Undefined();
-  HandleScope scope;
-  Handle<Value> arg = args[0];
-  String::Utf8Value value(arg);
-
-  printf("Logged: %s\n", *value);
-  fflush(stdout);
-
-  return v8::Undefined();
+  int r = oi_server_listen(&server, servinfo);
+  if(r == 0)
+    oi_server_attach(&server, loop);
+  return r;
 }
 
-int main 
-  ( int argc
-  , char *argv[]
-  ) 
+void
+Server::Stop() 
 {
-  loop = ev_default_loop(0);
+  oi_server_close (&server);
+  oi_server_detach (&server);
+}
 
+/* This constructor takes 2 arguments: host, port. */
+static Handle<Value>
+server_constructor (const Arguments& args) 
+{
+  if (args.Length() < 2)
+    return Undefined();
 
-  map<string, string> options;
-  string file;
-  ParseOptions(argc, argv, options, &file);
-  if (file.empty()) {
-    fprintf(stderr, "No script was specified.\n");
-    return 1;
-  }
   HandleScope scope;
-  Handle<String> source = ReadFile(file);
-  if (source.IsEmpty()) {
-    fprintf(stderr, "Error reading '%s'.\n", file.c_str());
-    return 1;
-  }
-
-  context = Context::New(NULL, ObjectTemplate::New());
-  Context::Scope context_scope(context);
-
-  Local<Object> g = Context::GetCurrent()->Global();
-  g->Set( String::New("log"), FunctionTemplate::New(LogCallback)->GetFunction());
-  g->Set( String::New("TCP"), tcp_initialize(loop));
-
-  // Compile and run the script
-  if (!compile(source))
-    return false;
-
-  // The script compiled and ran correctly.  Now we fetch out the
-  // Process function from the global object.
-  Handle<String> process_name = String::New("Process");
-  Handle<Value> process_val = context->Global()->Get(process_name);
 
-  // If there is no Process function, or if it is not a function,
-  // bail out
-  if (!process_val->IsFunction()) return false;
-
-  // It is a function; cast it to a Function
-  Handle<Function> process_fun = Handle<Function>::Cast(process_val);
-
-  // Store the function in a Persistent handle, since we also want
-  // that to remain after this call returns
-  process_ = Persistent<Function>::New(process_fun);
-
-  /////////////////////////////////////
-  /////////////////////////////////////
-  /////////////////////////////////////
-  
-  oi_server_init(&server, 1024);
-  server.on_connection = new_connection;
+  String::AsciiValue host(args[0]->ToString());
+  String::AsciiValue port(args[1]->ToString());
 
   // get addrinfo for localhost, PORT
   struct addrinfo *servinfo;
@@ -597,20 +520,44 @@ int main
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_PASSIVE;
-  int r = getaddrinfo(NULL, PORT, &hints, &servinfo);
-  assert(r == 0);
+  int r = getaddrinfo(NULL, *port, &hints, &servinfo);
+  if (r != 0)
+    return Undefined(); // XXX raise error?
+
+  //
+  //
+  //
+  // TODO host is ignored for now assumed localhost
+  //
+  //
+  //
+  //
+
+  Server *server = new Server(args.This());
+  if(server == NULL)
+    return Undefined(); // XXX raise error?
+
+  r = server->Start(servinfo);
+  if (r != 0)
+    return Undefined(); // XXX raise error?
+
+  printf("Running at http://localhost:%s/\n", *port);
+  return args.This();
+}
 
-  r = oi_server_listen(&server, servinfo);
-  assert(r == 0);
+Handle<Object> node_http_initialize (struct ev_loop *_loop)
+{
+  HandleScope scope;
 
-  oi_server_attach(&server, loop);
+  loop = _loop;
 
-  printf("Running at http://localhost:%s/\n", PORT);
+  Local<Object> http = Object::New();
 
-  ev_loop(loop, 0);
+  Local<FunctionTemplate> server_t = FunctionTemplate::New(server_constructor);
+  server_t->InstanceTemplate()->SetInternalFieldCount(1);
 
-  context.Dispose();
-  process_.Dispose();
+  http->Set(String::New("Server"), server_t->GetFunction());
 
-  return 0;
+  return scope.Close(http);
 }
diff --git a/node_http.h b/node_http.h
new file mode 100644 (file)
index 0000000..0080bd9
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef node_http_h
+#define node_http_h
+
+#include <v8.h>
+#include <ev.h>
+
+using namespace v8;
+
+Handle<Object> node_http_initialize (struct ev_loop *);
+
+#endif
similarity index 94%
rename from tcp.cc
rename to node_tcp.cc
index 83fc818..af18601 100644 (file)
--- a/tcp.cc
@@ -1,4 +1,4 @@
-#include "tcp.h"
+#include "node_tcp.h"
 
 #include <oi_socket.h>
 #include <oi_async.h>
@@ -88,6 +88,8 @@ static void resolve_done
 {
   TCPClient *client = static_cast<TCPClient*> (resolve_task->data);
 
+  printf("hello world\n");
+
   if(result != 0) {
     printf("error. TODO make call options error callback\n");
     client->options.Dispose();
@@ -95,7 +97,7 @@ static void resolve_done
     return;
   }
 
-  // Got the address succesfully. Let's connect now.  
+  printf("Got the address succesfully. Let's connect now.  \n");
 
   oi_socket_init(&client->socket, 30.0); // TODO adjustable timeout
 
@@ -145,8 +147,8 @@ static Handle<Value> Connect
   char host_s[host->Length()+1];  // + 1 for \0
   char port_s[port->Length()+1];
 
-  host->WriteAscii(host_s, 0, host->Length());
-  port->WriteAscii(port_s, 0, port->Length());
+  host->WriteAscii(host_s);
+  port->WriteAscii(port_s);
 
   printf("resolving host: %s, port: %s\n", host_s, port_s);
 
@@ -164,7 +166,7 @@ static Handle<Value> Connect
   oi_async_submit (&thread_pool, &client->resolve_task);
 }
 
-Handle<Object> tcp_initialize
+Handle<Object> node_tcp_initialize
   ( struct ev_loop *_loop
   )
 {
diff --git a/node_tcp.h b/node_tcp.h
new file mode 100644 (file)
index 0000000..4bc9fc3
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef node_tcp_h
+#define node_tcp_h
+
+#include <v8.h>
+#include <ev.h>
+
+using namespace v8;
+
+Handle<Object> node_tcp_initialize (struct ev_loop *);
+
+#endif
diff --git a/tcp.h b/tcp.h
deleted file mode 100644 (file)
index d42b6c0..0000000
--- a/tcp.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef tcp_h
-#define tcp_h
-
-#include <v8.h>
-#include <ev.h>
-
-using namespace v8;
-
-Handle<Object> tcp_initialize (struct ev_loop *);
-
-#endif