From 20c0e1fdfba2a237428332664a76b8b70e0dab9f Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 26 Jun 2009 18:29:57 +0200 Subject: [PATCH] events for http.Server --- src/http.js | 516 ++++++++++++++++++++++++++-------------------------- 1 file changed, 262 insertions(+), 254 deletions(-) diff --git a/src/http.js b/src/http.js index bbd163ff01..848265a8d6 100644 --- a/src/http.js +++ b/src/http.js @@ -123,7 +123,119 @@ function send (output, data, encoding) { output.push([data, encoding]); }; -node.http.ServerResponse = function (connection, responses) { +/* This is a wrapper around the LowLevelServer interface. It provides + * connection handling, overflow checking, and some data buffering. + */ +node.http.createServer = function (requestListener, options) { + var server = new node.http.LowLevelServer(); + server.addListener("Connection", connectionListener); + server.addListener("Request", requestListener); + //server.setOptions(options); + return server; +}; + +node.http.createServerRequest = function (connection) { + var req = new node.EventEmitter; + + req.connection = connection; + req.method = null; + req.uri = ""; + req.httpVersion = null; + req.headers = []; + req.last_was_value = false; // used internally XXX remove me + + req.setBodyEncoding = function (enc) { + connection.setEncoding(enc); + }; + + return req; +}; + +// ^ +// | +// | combine these two functions +// | +// v + +createClientResponse = function (connection) { + var res = new node.EventEmitter; + + res.connection = connection; + res.statusCode = null; + res.httpVersion = null; + res.headers = []; + res.last_was_value = false; // used internally XXX remove me + + res.setBodyEncoding = function (enc) { + connection.setEncoding(enc); + }; + + return res; +}; + +function connectionListener (connection) { + // An array of responses for each connection. In pipelined connections + // we need to keep track of the order they were sent. + connection.responses = []; + + // is this really needed? + connection.addListener("EOF", function () { + if (connection.responses.length == 0) { + connection.close(); + } else { + connection.responses[connection.responses.length-1].closeOnFinish = true; + } + }); + + var req, res; + + connection.addListener("MessageBegin", function () { + req = new node.http.createServerRequest(connection); + res = new node.http.ServerResponse(connection); + }); + + connection.addListener("URI", function (data) { + req.uri += data; + }); + + connection.addListener("HeaderField", function (data) { + if (req.headers.length > 0 && req.last_was_value == false) + req.headers[req.headers.length-1][0] += data; + else + req.headers.push([data]); + req.last_was_value = false; + }); + + connection.addListener("HeaderValue", function (data) { + var last_pair = req.headers[req.headers.length-1]; + if (last_pair.length == 1) + last_pair[1] = data; + else + last_pair[1] += data; + req.last_was_value = true; + }); + + connection.addListener("HeadersComplete", function (info) { + req.httpVersion = info.httpVersion; + req.method = info.method; + req.uri = node.http.parseUri(req.uri); // TODO parse the URI lazily? + + res.should_keep_alive = info.should_keep_alive; + + connection.server.emit("Request", [req, res]); + }); + + connection.addListener("Body", function (chunk) { + req.emit("Body", [chunk]); + }); + + connection.addListener("MessageComplete", function () { + req.emit("BodyComplete"); + }); +}; + +node.http.ServerResponse = function (connection) { + var responses = connection.responses; responses.push(this); this.connection = connection; this.closeOnFinish = false; @@ -226,301 +338,198 @@ node.http.ServerResponse = function (connection, responses) { }; }; -/* This is a wrapper around the LowLevelServer interface. It provides - * connection handling, overflow checking, and some data buffering. - */ -node.http.createServer = function (requestHandler, options) { - var server = new node.http.LowLevelServer(); - server.addListener("Connection", node.http.connectionListener); - //server.setOptions(options); - server.requestHandler = requestHandler; - return server; -}; -node.http.connectionListener = function (connection) { - // An array of responses for each connection. In pipelined connections - // we need to keep track of the order they were sent. - var responses = []; +node.http.Client = node.http.LowLevelClient; // FIXME - // is this really needed? - connection.addListener("EOF", function () { - if (responses.length == 0) { - connection.close(); - } else { - responses[responses.length-1].closeOnFinish = true; - } - }); +node.http.createClient = function (port, host) { + var client = new node.http.Client(); + var requests = client.requests = []; - connection.onMessage = function () { - var interrupted = false; - // filled in ... - var req = { method : null // at onHeadersComplete - , uri : "" // at onURI - , httpVersion : null // at onHeadersComplete - , headers : [] // at onHeaderField, onHeaderValue - , onBody : null // by user - , onBodyComplete : null // by user - , interrupt : function ( ) { interrupted = true; } - , setBodyEncoding : function (enc) { - connection.setEncoding(enc); - } - }; - var res = new node.http.ServerResponse(connection, responses); - - this.onURI = function (data) { - req.uri += data; - return !interrupted; - }; - - var last_was_value = false; - var headers = req.headers; + client.reconnect = function () { return client.connect(port, host) }; - this.onHeaderField = function (data) { - if (headers.length > 0 && last_was_value == false) - headers[headers.length-1][0] += data; - else - headers.push([data]); - last_was_value = false; - return !interrupted; - }; + client.addListener("Connect", function () { + //node.debug("HTTP CLIENT onConnect. readyState = " + client.readyState); + //node.debug("requests[0].uri = '" + requests[0].uri + "'"); + requests[0].flush(); + }); - this.onHeaderValue = function (data) { - var last_pair = headers[headers.length-1]; - if (last_pair.length == 1) - last_pair[1] = data; - else - last_pair[1] += data; - last_was_value = true; - return !interrupted; - }; + client.addListener("EOF", function () { + client.close(); + }); - this.onHeadersComplete = function () { - req.httpVersion = this.httpVersion; - req.method = this.method; - // TODO parse the URI lazily? - req.uri = node.http.parseUri(req.uri); - res.should_keep_alive = this.should_keep_alive; + client.addListener("Disconnect", function (had_error) { + if (had_error) { + client.emit("Error"); + return; + } + + //node.debug("HTTP CLIENT onDisconnect. readyState = " + client.readyState); + // If there are more requests to handle, reconnect. + if (requests.length > 0) { + //node.debug("HTTP CLIENT: reconnecting"); + client.connect(port, host); + } + }); - connection.server.requestHandler.apply(connection.server, [req, res]); + var req, res; - return !interrupted; - }; + client.addListener("MessageBegin", function () { + req = requests.shift(); + res = createClientResponse(client); + }); - this.onBody = function (chunk) { - if (req.onBody) req.onBody(chunk); - return !interrupted; - }; + client.addListener("HeaderField", function (data) { + if (res.headers.length > 0 && res.last_was_value == false) + res.headers[res.headers.length-1][0] += data; + else + res.headers.push([data]); + res.last_was_value = false; + }); - this.onMessageComplete = function () { - if (req.onBodyComplete) req.onBodyComplete(); - return !interrupted; - }; - }; -}; + client.addListener("HeaderValue", function (data) { + var last_pair = res.headers[res.headers.length-1]; + if (last_pair.length == 1) { + last_pair[1] = data; + } else { + last_pair[1] += data; + } + res.last_was_value = true; + }); -node.http.Client = function (port, host) { - var connection = new node.http.LowLevelClient(); - var requests = []; - var self = this; + client.addListener("HeadersComplete", function (info) { + res.statusCode = info.statusCode; + res.httpVersion = info.httpVersion; - function ClientRequest (method, uri, header_lines) { - this.uri = uri; + req.emit("Response", [res]); + }); - var chunked_encoding = false; - this.closeOnFinish = false; + client.addListener("Body", function (chunk) { + res.emit("Body", [chunk]); + }); - var sent_connection_header = false; - var sent_transfer_encoding_header = false; - var sent_content_length_header = false; + client.addListener("MessageComplete", function () { + client.close(); + res.emit("BodyComplete"); + }); - var header = method + " " + uri + " HTTP/1.1\r\n"; + return client; +}; - header_lines = header_lines || []; - for (var i = 0; i < header_lines.length; i++) { - var field = header_lines[i][0]; - var value = header_lines[i][1]; +node.http.Client.prototype.get = function (uri, headers) { + return createClientRequest(this, "GET", uri, headers); +}; - header += field + ": " + value + CRLF; - - if (connection_expression.exec(field)) { - sent_connection_header = true; - if (close_expression.exec(value)) this.closeOnFinish = true; - } else if (transfer_encoding_expression.exec(field)) { - sent_transfer_encoding_header = true; - if (chunk_expression.exec(value)) chunked_encoding = true; - } else if (content_length_expression.exec(field)) { - sent_content_length_header = true; - } - } +node.http.Client.prototype.head = function (uri, headers) { + return createClientRequest(this, "HEAD", uri, headers); +}; - if (sent_connection_header == false) { - header += "Connection: keep-alive\r\n"; - } +node.http.Client.prototype.post = function (uri, headers) { + return createClientRequest(this, "POST", uri, headers); +}; - header += CRLF; - - var output = []; - send(output, header); +node.http.Client.prototype.del = function (uri, headers) { + return createClientRequest(this, "DELETE", uri, headers); +}; - this.sendBody = function (chunk, encoding) { - if (sent_content_length_header == false && chunked_encoding == false) { - throw "Content-Length header (or Transfer-Encoding:chunked) not set"; - return; - } +node.http.Client.prototype.put = function (uri, headers) { + return createClientRequest(this, "PUT", uri, headers); +}; - if (chunked_encoding) { - send(output, chunk.length.toString(16)); - send(output, CRLF); - send(output, chunk, encoding); - send(output, CRLF); - } else { - send(output, chunk, encoding); - } +function createClientRequest (connection, method, uri, header_lines) { + var req = new node.EventEmitter; + var requests = connection.requests; - this.flush(); - }; + requests.push(this); - this.flush = function ( ) { - if (connection.readyState == "closed") { - connection.connect(port, host); - return; - } - //node.debug("HTTP CLIENT flush. readyState = " + connection.readyState); - while ( this === requests[0] - && output.length > 0 - && connection.readyState == "open" - ) - { - var out = output.shift(); - connection.send(out[0], out[1]); - } - }; + req.uri = uri; - this.finished = false; - this.finish = function (responseHandler) { - this.responseHandler = responseHandler; - if (chunked_encoding) - send(output, "0\r\n\r\n"); // last chunk + var chunked_encoding = false; + req.closeOnFinish = false; + + var sent_connection_header = false; + var sent_transfer_encoding_header = false; + var sent_content_length_header = false; + + var header = method + " " + uri + " HTTP/1.1\r\n"; + + header_lines = header_lines || []; + for (var i = 0; i < header_lines.length; i++) { + var field = header_lines[i][0]; + var value = header_lines[i][1]; + + header += field + ": " + value + CRLF; + + if (connection_expression.exec(field)) { + sent_connection_header = true; + if (close_expression.exec(value)) req.closeOnFinish = true; + } else if (transfer_encoding_expression.exec(field)) { + sent_transfer_encoding_header = true; + if (chunk_expression.exec(value)) chunked_encoding = true; + } else if (content_length_expression.exec(field)) { + sent_content_length_header = true; + } + } - this.flush(); - }; + if (sent_connection_header == false) { + header += "Connection: keep-alive\r\n"; } - - connection.onConnect = function () { - //node.debug("HTTP CLIENT onConnect. readyState = " + connection.readyState); - //node.debug("requests[0].uri = '" + requests[0].uri + "'"); - requests[0].flush(); - }; - connection.onEOF = function () { - connection.close(); - }; + header += CRLF; + + var output = []; + send(output, header); - connection.onDisconnect = function (had_error) { - if (had_error) { - if (self.onError) self.onError(); + req.sendBody = function (chunk, encoding) { + if (sent_content_length_header == false && chunked_encoding == false) { + throw "Content-Length header (or Transfer-Encoding:chunked) not set"; return; } - - //node.debug("HTTP CLIENT onDisconnect. readyState = " + connection.readyState); - // If there are more requests to handle, reconnect. - if (requests.length > 0) { - //node.debug("HTTP CLIENT: reconnecting"); - this.connect(port, host); - } - }; - - // On response - connection.onMessage = function () { - var req = requests.shift(); - var res = { statusCode : null // set in onHeadersComplete - , httpVersion : null // set in onHeadersComplete - , headers : [] // set in onHeaderField/Value - , setBodyEncoding : function (enc) { - connection.setEncoding(enc); - } - }; - - var headers = res.headers; - var last_was_value = false; - - this.onHeaderField = function (data) { - if (headers.length > 0 && last_was_value == false) - headers[headers.length-1][0] += data; - else - headers.push([data]); - last_was_value = false; - return true; - }; - this.onHeaderValue = function (data) { - var last_pair = headers[headers.length-1]; - if (last_pair.length == 1) { - last_pair[1] = data; - } else { - last_pair[1] += data; - } - last_was_value = true; - return true; - }; - - this.onHeadersComplete = function () { - res.statusCode = this.statusCode; - res.httpVersion = this.httpVersion; - res.headers = headers; - - req.responseHandler(res); - return true; - }; - - this.onBody = function (chunk) { - if (res.onBody) { - return res.onBody(chunk); - } else { - return true; - } - }; + if (chunked_encoding) { + send(output, chunk.length.toString(16)); + send(output, CRLF); + send(output, chunk, encoding); + send(output, CRLF); + } else { + send(output, chunk, encoding); + } - this.onMessageComplete = function () { - connection.close(); - if (res.onBodyComplete) { - return res.onBodyComplete(); - } else { - return true; - } - }; + req.flush(); }; - function newRequest (method, uri, headers) { - var req = new ClientRequest(method, uri, headers); - requests.push(req); - return req; - } - - this.get = function (uri, headers) { - return newRequest("GET", uri, headers); + req.flush = function ( ) { + if (connection.readyState == "closed") { + connection.reconnect(); + return; + } + //node.debug("HTTP CLIENT flush. readyState = " + connection.readyState); + while ( req === requests[0] + && output.length > 0 + && connection.readyState == "open" + ) + { + var out = output.shift(); + connection.send(out[0], out[1]); + } }; - this.head = function (uri, headers) { - return newRequest("HEAD", uri, headers); - }; + req.finished = false; - this.post = function (uri, headers) { - return newRequest("POST", uri, headers); - }; + req.finish = function (responseListener) { + req.addListener("Response", responseListener); - this.del = function (uri, headers) { - return newRequest("DELETE", uri, headers); - }; + if (chunked_encoding) + send(output, "0\r\n\r\n"); // last chunk - this.put = function (uri, headers) { - return newRequest("PUT", uri, headers); + req.flush(); }; -}; + + return req; +} node.http.cat = function (url, encoding, callback) { var uri = node.http.parseUri(url); - var req = new node.http.Client(uri.port || 80, uri.host).get(uri.path || "/"); + var req = node.http.createClient(uri.port || 80, uri.host).get(uri.path || "/"); req.finish(function (res) { var status = res.statusCode == 200 ? 0 : -1; res.setBodyEncoding(encoding); @@ -535,4 +544,3 @@ node.http.cat = function (url, encoding, callback) { }; })(); // anonymous namespace -