From a80591aff6704bd71ac5b136e23ddd7b52cf0299 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 6 May 2009 14:54:28 +0200 Subject: [PATCH] Create node.http.Server and node.http.LowLevelServer The LowLevelServer is a direct interface to the parser given people access to things like partially received headers. This could be used to implement an extremely optimized server which acts before parsing is complete. Most people will be using node.http.Server which is still rather low-level compared to other http interfaces, but does take care of some details for you. --- src/http.cc | 183 ++++++++++++++++++++++++++---------------------------------- src/http.js | 140 ++++++++++++++++++++++++++++++++++++++-------- src/node.cc | 6 +- 3 files changed, 202 insertions(+), 127 deletions(-) diff --git a/src/http.cc b/src/http.cc index 45e2f95..bef2638 100644 --- a/src/http.cc +++ b/src/http.cc @@ -11,10 +11,12 @@ #define ON_BODY_SYMBOL String::NewSymbol("onBody") #define ON_MESSAGE_COMPLETE_SYMBOL String::NewSymbol("onMessageComplete") -#define PATH_SYMBOL String::NewSymbol("path") -#define QUERY_STRING_SYMBOL String::NewSymbol("query_string") -#define URI_SYMBOL String::NewSymbol("uri") -#define FRAGMENT_SYMBOL String::NewSymbol("fragment") +#define ON_PATH_SYMBOL String::NewSymbol("onPath") +#define ON_QUERY_STRING_SYMBOL String::NewSymbol("onQueryString") +#define ON_URI_SYMBOL String::NewSymbol("onURI") +#define ON_FRAGMENT_SYMBOL String::NewSymbol("onFragment") +#define ON_HEADER_FIELD_SYMBOL String::NewSymbol("onHeaderField") +#define ON_HEADER_VALUE_SYMBOL String::NewSymbol("onHeaderValue") #define STATUS_CODE_SYMBOL String::NewSymbol("status_code") #define HTTP_VERSION_SYMBOL String::NewSymbol("http_version") @@ -23,56 +25,12 @@ using namespace v8; using namespace node; using namespace std; -// Native Helper Functions - -static Persistent _fill_field; -static Persistent _append_header_field; -static Persistent _append_header_value; - -#define CATCH_NATIVE_HTTP_FUNCTION(variable, jsname) \ -do { \ - if (variable.IsEmpty()) { \ - Local __g = Context::GetCurrent()->Global(); \ - Local __node_v = __g->Get(String::NewSymbol("node")); \ - Local __node = __node_v->ToObject(); \ - Local __http_v = __node->Get(String::NewSymbol("http")); \ - Local __http = __http_v->ToObject(); \ - Local __value = __http->Get(String::NewSymbol(jsname)); \ - Handle __function_handle = Handle::Cast(__value); \ - variable = Persistent::New(__function_handle); \ - } \ -} while(0) - -void -fillField (Handle message, Handle field, Handle value) -{ - HandleScope scope; - CATCH_NATIVE_HTTP_FUNCTION(_fill_field, "fillField"); - Handle argv[] = { message, field, value }; - _fill_field->Call(message->ToObject(), 3, argv); -} - -void -appendHeaderField (Handle message, Handle d) -{ - HandleScope scope; - CATCH_NATIVE_HTTP_FUNCTION(_append_header_field, "appendHeaderField"); - Handle argv[] = { message, d }; - _append_header_field->Call(message->ToObject(), 2, argv); -} - -void -appendHeaderValue (Handle message, Handle d) -{ - HandleScope scope; - CATCH_NATIVE_HTTP_FUNCTION(_append_header_value, "appendHeaderValue"); - Handle argv[] = { message, d }; - _append_header_value->Call(message->ToObject(), 2, argv); -} Persistent HTTPConnection::client_constructor_template; Persistent HTTPConnection::server_constructor_template; +static Persistent http_module; + void HTTPConnection::Initialize (Handle target) { @@ -82,13 +40,13 @@ HTTPConnection::Initialize (Handle target) client_constructor_template = Persistent::New(t); client_constructor_template->Inherit(Connection::constructor_template); client_constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - target->Set(String::NewSymbol("HTTPClient"), client_constructor_template->GetFunction()); + target->Set(String::NewSymbol("Client"), client_constructor_template->GetFunction()); t = FunctionTemplate::New(v8NewServer); server_constructor_template = Persistent::New(t); server_constructor_template->Inherit(Connection::constructor_template); server_constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - target->Set(String::NewSymbol("HTTPServerSideSocket"), + target->Set(String::NewSymbol("ServerSideSocket"), server_constructor_template->GetFunction()); } @@ -119,13 +77,8 @@ HTTPConnection::OnReceive (const void *buf, size_t len) { http_parser_execute(&parser_, static_cast(buf), len); - if (http_parser_has_error(&parser_)) { - // do something? - Close(); - return; - } - - // XXX when do we close the connection? + if (http_parser_has_error(&parser_)) + ForceClose(); } int @@ -138,48 +91,50 @@ HTTPConnection::on_message_begin (http_parser *parser) Local on_message_v = protocol->Get(ON_MESSAGE_SYMBOL); if (!on_message_v->IsFunction()) return -1; Handle on_message = Handle::Cast(on_message_v); + + TryCatch try_catch; Local message_handler = on_message->NewInstance(); + if (try_catch.HasCaught()) { + fatal_exception(try_catch); + return -1; + } + connection->handle_->SetHiddenValue(MESSAGE_HANDLER_SYMBOL, message_handler); return 0; } -#define DEFINE_FILL_VALUE_CALLBACK(callback_name, symbol) \ -int \ -HTTPConnection::callback_name (http_parser *parser, const char *buf, size_t len) \ -{ \ - HTTPConnection *connection = static_cast (parser->data); \ - HandleScope scope; \ - Local message_handler_v = \ - connection->handle_->GetHiddenValue(MESSAGE_HANDLER_SYMBOL); \ - fillField(message_handler_v, symbol, String::New(buf, len)); \ - return 0; \ +#define DEFINE_PARSER_CALLBACK(name, symbol) \ +int \ +HTTPConnection::name (http_parser *parser, const char *buf, size_t len) \ +{ \ + HandleScope scope; \ + HTTPConnection *connection = static_cast (parser->data); \ + Local message_handler_v = \ + connection->handle_->GetHiddenValue(MESSAGE_HANDLER_SYMBOL); \ + if (message_handler_v->IsObject() == false) \ + return -1; \ + Local message_handler = message_handler_v->ToObject(); \ + Local callback_v = message_handler->Get(symbol); \ + if (callback_v->IsFunction() == false) \ + return 0; \ + Local callback = Local::Cast(callback_v); \ + TryCatch try_catch; \ + Local argv[1] = { String::New(buf, len) }; \ + Local ret = callback->Call(message_handler, 1, argv); \ + if (ret.IsEmpty()) { \ + fatal_exception(try_catch); \ + return -2; \ + } \ + if (ret->IsFalse()) return -3; \ + return 0; \ } -DEFINE_FILL_VALUE_CALLBACK(on_path, PATH_SYMBOL) -DEFINE_FILL_VALUE_CALLBACK(on_query_string, QUERY_STRING_SYMBOL) -DEFINE_FILL_VALUE_CALLBACK(on_uri, URI_SYMBOL) -DEFINE_FILL_VALUE_CALLBACK(on_fragment, FRAGMENT_SYMBOL) -int -HTTPConnection::on_header_field (http_parser *parser, const char *buf, size_t len) -{ - HTTPConnection *connection = static_cast (parser->data); - HandleScope scope; - Local message_handler_v = - connection->handle_->GetHiddenValue(MESSAGE_HANDLER_SYMBOL); - appendHeaderField(message_handler_v, String::New(buf, len)); - return 0; -} - -int -HTTPConnection::on_header_value (http_parser *parser, const char *buf, size_t len) -{ - HTTPConnection *connection = static_cast (parser->data); - HandleScope scope; - Local message_handler_v = - connection->handle_->GetHiddenValue(MESSAGE_HANDLER_SYMBOL); - appendHeaderValue(message_handler_v, String::New(buf, len)); - return 0; -} +DEFINE_PARSER_CALLBACK(on_path, ON_PATH_SYMBOL) +DEFINE_PARSER_CALLBACK(on_query_string, ON_QUERY_STRING_SYMBOL) +DEFINE_PARSER_CALLBACK(on_uri, ON_URI_SYMBOL) +DEFINE_PARSER_CALLBACK(on_fragment, ON_FRAGMENT_SYMBOL) +DEFINE_PARSER_CALLBACK(on_header_field, ON_HEADER_FIELD_SYMBOL) +DEFINE_PARSER_CALLBACK(on_header_value, ON_HEADER_VALUE_SYMBOL) int HTTPConnection::on_headers_complete (http_parser *parser) @@ -211,8 +166,13 @@ HTTPConnection::on_headers_complete (http_parser *parser) Handle on_headers_complete = Handle::Cast(on_headers_complete_v); - - on_headers_complete->Call(message_handler, 0, NULL); + TryCatch try_catch; + Local ret = on_headers_complete->Call(message_handler, 0, NULL); + if (ret.IsEmpty()) { + fatal_exception(try_catch); + return -2; + } + if (ret->IsFalse()) return -3; return 0; } @@ -235,8 +195,8 @@ HTTPConnection::on_body (http_parser *parser, const char *buf, size_t len) Handle argv[1]; - // XXX whose encoding should we check? each message should have their own, - // probably. it ought to default to raw. + // TODO each message should have their encoding. + // don't look at the conneciton for encoding if(connection->encoding_ == UTF8) { // utf8 encoding Handle chunk = String::New((const char*)buf, len); @@ -252,6 +212,15 @@ HTTPConnection::on_body (http_parser *parser, const char *buf, size_t len) argv[0] = array; } on_body->Call(message_handler, 1, argv); + + TryCatch try_catch; + Local ret = on_body->Call(message_handler, 0, NULL); + if (ret.IsEmpty()) { + fatal_exception(try_catch); + return -2; + } + if (ret->IsFalse()) return -3; + return 0; } @@ -264,13 +233,21 @@ HTTPConnection::on_message_complete (http_parser *parser) Local message_handler_v = connection->handle_->GetHiddenValue(MESSAGE_HANDLER_SYMBOL); Local message_handler = message_handler_v->ToObject(); + connection->handle_->DeleteHiddenValue(MESSAGE_HANDLER_SYMBOL); Local on_msg_complete_v = message_handler->Get(ON_MESSAGE_COMPLETE_SYMBOL); - if (on_msg_complete_v->IsFunction()) { - Handle on_msg_complete = Handle::Cast(on_msg_complete_v); - on_msg_complete->Call(message_handler, 0, NULL); + if (on_msg_complete_v->IsFunction() == false) + return 0; + Handle on_msg_complete = Handle::Cast(on_msg_complete_v); + + TryCatch try_catch; + Local ret = on_msg_complete->Call(message_handler, 0, NULL); + if (ret.IsEmpty()) { + fatal_exception(try_catch); + return -2; } - connection->handle_->DeleteHiddenValue(MESSAGE_HANDLER_SYMBOL); + if (ret->IsFalse()) return -3; + return 0; } @@ -302,7 +279,7 @@ HTTPServer::Initialize (Handle target) constructor_template = Persistent::New(t); constructor_template->Inherit(Acceptor::constructor_template); constructor_template->InstanceTemplate()->SetInternalFieldCount(1); - target->Set(String::NewSymbol("HTTPServer"), constructor_template->GetFunction()); + target->Set(String::NewSymbol("LowLevelServer"), constructor_template->GetFunction()); } Handle diff --git a/src/http.js b/src/http.js index 28128ee..a39d837 100644 --- a/src/http.js +++ b/src/http.js @@ -1,25 +1,121 @@ -node.http = { - fillField : function (msg, field, data) { - msg[field] = (msg[field] || "") + data; - }, - - appendHeaderField : function (msg, data) { - if (msg.hasOwnProperty("headers")) { - var last_pair = msg.headers[msg.headers.length-1]; - if (last_pair.length == 1) - last_pair[0] += data; - else - msg.headers.push([data]); - } else { - msg.headers = [[data]]; +/* This is a wrapper around the LowLevelServer interface. It provides + * connection handling, overflow checking, and some data buffering. + */ +node.http.Server = function (RequestHandler, options) { + var MAX_FIELD_SIZE = 80*1024; + function Protocol (connection) { + function fillField (field, data) { + field = (field || "") + data; + if (field.length > MAX_FIELD_SIZE) { + connection.fullClose(); + return false; + } + return true; } - }, - - appendHeaderValue : function (msg, data) { - var last_pair = msg.headers[msg.headers.length-1]; - if (last_pair.length == 1) - last_pair[1] = data; - else - last_pair[1] += data; + + var responses = []; + function Response () { + responses.push(this); + /* This annoying output buisness is necessary for the case that users + * are writing to responses out of order! HTTP requires that responses + * are returned in the same order the requests come. + */ + var output = ""; + this.output = output; + + function send (data) { + if (responses[0] === this) { + connection.send(data); + } else { + output += data; + } + }; + + this.sendStatus = function (status, reason) { + output += "HTTP/1.1 " + status.toString() + " " + reason + "\r\n"; + }; + + this.sendHeader = function (field, value) { + output += field + ": " + value.toString() + "\r\n"; + }; + + var headersSent = false; + + this.sendBody = function (chunk) { + if (headersSent === false) { + output += "\r\n"; + } + output += chunk; + if (responses[0] === this) { + connection.send(output); + output = ""; + } + }; + + var finished = false; + this.finish = function () { + this.finished = true; + while (responses.length > 0 && responses[0].finished) { + var res = responses.shift(); + connection.send(res.output); + } + + if (responses.length == 0) { + connection.fullClose(); + // TODO keep-alive logic + // if http/1.0 without "Connection: keep-alive" header - yes + // if http/1.1 if the request was not GET or HEAD - yes + // if http/1.1 without "Connection: close" header - yes + } + }; + } + + this.onMessage = function ( ) { + var res = new Response(); + var req = new RequestHandler(res); + + this.encoding = req.encoding || "raw"; + + this.onPath = function (data) { return fillField(req.path, data); }; + this.onURI = function (data) { return fillField(req.uri, data); }; + this.onQueryString = function (data) { return fillField(req.query_string, data); }; + this.onFragment = function (data) { return fillField(req.fragment, data); }; + + this.onHeaderField = function (data) { + if (req.hasOwnProperty("headers")) { + var last_pair = req.headers[req.headers.length-1]; + if (last_pair.length == 1) + return fillField(last_pair[0], data); + else + req.headers.push([data]); + } else { + req.headers = [[data]]; + } + return true; + }; + + this.onHeaderValue = function (data) { + var last_pair = req.headers[req.headers.length-1]; + if (last_pair.length == 1) + last_pair[1] = data; + else + return fillField(last_pair[1], data); + return true; + }; + + this.onHeadersComplete = function () { + req.http_version = this.http_version; + req.method = this.method; + return req.onHeadersComplete(); + }; + + this.onBody = function (chunk) { return req.onBody(chunk); }; + + this.onBodyComplete = function () { return req.onBodyComplete(); }; + }; } + + var server = new node.http.LowLevelServer(Protocol, options); + this.listen = function (port, host) { server.listen(port, host); } + this.close = function () { server.close(); } }; diff --git a/src/node.cc b/src/node.cc index da1b098..cb16453 100644 --- a/src/node.cc +++ b/src/node.cc @@ -270,8 +270,10 @@ main (int argc, char *argv[]) Acceptor::Initialize(g); Connection::Initialize(g); - HTTPServer::Initialize(g); - HTTPConnection::Initialize(g); + Local http = Object::New(); + node->Set(String::New("http"), http); + HTTPServer::Initialize(http); + HTTPConnection::Initialize(http); // NATIVE JAVASCRIPT MODULES TryCatch try_catch; -- 2.7.4