new object $http_connection: $string_connection; var $root inited = 1; var $http_connection method = 0; var $http_connection page = 0; var $http_connection args = 0; var $http_connection version = 0; var $http_connection headers = #[]; public method .init_http_connection() { .debug("Anew!"); headers = #[]; }; public method .headers() { return headers; }; private method .respond() { arg code, head, text; var len, item, x; // Set up keepalive, if they've asked for it. if (headers["Connection"] != "Keep-Alive") { head = head.add("Connection", "Close"); } if (version) { // if version isn't defined, it's HTTP/0.9. That means no headers. None. // First, set these headers if they haven't been already. if (! (| head["Server"] |)) { head = head.add("Server", $http_lib.server()); } if (! (| head["Content-type"] |)) { head = head.add("Content-type", "text/plain"); } if (! (| head["Content-length"] |)) { len = (| text.mmap('length).sum() + text.length() |) || 0; head = head.add("Content-length", tostr(len)); } // First thing out is the response header .write(version + " " + code + " " + $http_lib.response_phrase(code)); for item in (head) { if (type(item[2]) == 'list) { // If it's a list, they want multiple headers with the same name map x in (item[2]) to (item[1] + ": " + item[2]); } else { .write(item[1] + ": " + item[2]); } } .write(""); } if (method != "HEAD") { // Print out the body. The HEAD method means they just want the header :) switch ((| text[1] |)) { case 'file: // Write out the file. This saves lots of space and time for big files cwritef(text[2]); default: .write(text); } } if ((| head["Connection"] |) == "Close") { .close(); } }; public method .error() { arg code, @text; var head; // Just a wrapper to make life simpler. [code, head, text] = $http_lib.error(code, @text); // Make sure method isn't HEAD, so the body will be sent out method = ""; // RFC2068 says these responses should have *no* body. if (version && (((code >= 100) && (code < 200)) || (code == 204) || (code == 304))) { text = []; } return .respond(code, head, text); }; public method .parse_line() { arg line; var URI, parts, i; if (!method) { // If method is false, that means this is the first line. The first // line must be a request line. catch ~range { // Requests can either be two or three words. If three, the third // is the version. [method, URI, @i] = line.explode(); if (i) { [version, @i] = i; } } with { // If ~range was thrown, it means that not enough word were sent. i = 1; } if (i) { // A malformed request return .error(400); } // HTTP/1.1 and beyond have keepalive on by default. if (!version || (version = "HTTP/1.0")) { headers = headers.add("Connection", "Close"); } else { headers = headers.add("Connection", "Keep-Alive"); } // HTTP/0.9 can't send a HEAD, since there are no headers in that protocol! if (!version && (method == "HEAD")) { return .error(501); } // unsupported_methods = ["OPTIONS", "POST", "PUT", "DELETE", "TRACE"]; if (method in ["GET", "HEAD"]) { if ((parts = URI.regexp("^(http://([-A-Za-z0-9.]+))?/?([^/]*)(.*)"))) { page = parts[3]; args = parts[4]; } else { page = URI; } // If HTTP/0.9, fall through so we can send the page if (version) return; } else { return .error(501, method + " to " + URI + " not supported."); } } // If the line is blank, or if we're speaking HTTP/0.9 if ((line == "") || (!version)) { catch any { return .respond(@(> lookup(tosym("http_page_" + page)).generate(page, args) <)); } with { if (error() == ~namenf) { // The infamous 404, file not found return .error(404); } else { i = "<PRE>" + $parse_lib.traceback(traceback()).join("<BR>") + "</PRE>"; // Server error return .error(500, i); } } } else { i = stridx(line, ": ", 1); headers = headers.add(substr(line, 1, i-1), substr(line, i+2)); } };