diff --git a/src/http.cc b/src/http.cc index 45e2f95b3c..bef2638df3 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 28128ee4a3..a39d837d53 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 da1b0986db..cb16453efd 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;