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 = #[];
// Big denial-of-service problem here with the keepalive
// If this comment is here, it means that I forgot to plug it up. Bug me about it.
public method .init_http_connection() {
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));
}
};