// ------------------------------------------------------------------
//
// Object $connection (Generic connection object)
//
// This handles inbound and outbound connections
//
// for inbound, the connection which binds to the port becomes the
// 'daemon', and it reassigns incoming connections to spawned children.
//
// ------------------------------------------------------------------
new object $connection: $root;
var host = 0;
var port = 0;
public method .host() {
return host;
};
public method .set_host() {
arg new_host;
host = new_host;
};
public method .port() {
return port;
};
public method .startup() {
arg start_port;
port = start_port;
$sys.log("** Starting httpd on port " + tostr(port) + " **");
bind_port(port);
};
driver method .connect() {
arg client, server, socket;
var d;
d = .spawn();
reassign_connection(d);
d.set_host(client);
};
driver method .disconnect() {
.close();
};
// override on actual descendants to do something
driver method .parse() {
arg incoming;
};
public method .close() {
(| close_connection() |);
.destroy();
};
// ------------------------------------------------------------------
//
// Object $http_connection
//
// HTTP-specific connection object
//
// handles: GET and POST
//
new object $http_connection: $connection;
var header = #[];
var method = "";
var http = "";
var status = 0;
var URL = "";
var bytes = 0;
var ctype = "";
var prefix = "";
var buffer = `[];
var reading = 0;
var info = #[];
var keep_alive = 0;
root method .init_http_connection() {
.reset_variables();
};
public method .parse() {
arg incoming;
var l, line, i, t;
catch any {
buffer += incoming;
while (!reading) {
if (!(i = `[13, 10] in buffer))
break;
line = buf_to_str(subbuf(buffer, 1, i - 1));
buffer = subbuf(buffer, i + 2);
(> .process_line(line) <);
}
} with handler {
.respond(traceback().tb_to_html(status = 500));
.close();
$sys.log(traceback().tb_to_text());
}
};
public method .reset_variables() {
header = #[];
method = "";
http = "HTTP/1.0";
status = 200;
URL = "";
prefix = "";
ctype = "text/html";
buffer = `[];
reading = 0;
keep_alive = 0;
};
public method .close() {
arg [args];
if (!args && keep_alive)
return;
pass();
};
public method .die() {
arg code, message;
return .respond($sys.response(status = code, message));
};
// die on HTTP/0.9, we are simply required to understand it,
// not handle it.
public method .process_line() {
arg line;
var match;
if (!method) {
line = explode(line);
if (listlen(line) != 3) {
cwrite(str_to_buf($sys.response(400, "HTTP/0.9 not supported.")));
.close();
.reset_variables();
return;
}
if ((match = regexp("^GET|POST", line[1])))
method = match[1];
else
return .die(405, "Method: \"" + line[1] + "\".");
URL = line[2];
http = line[3];
} else if (line) {
// record the header
if ((match = regexp("^([^:]+): *(.+)$", line))) {
if (match[1] == "Connection" && match[2] == "Keep-Alive")
keep_alive = 1;
else
header = header.add_elem(@match);
}
} else {
// Non line, parse the URL and go with it
info = URL.explode_url();
URL = "/" + info['path].join("/");
catch any {
if (method == "POST")
(> .method_POST() <);
else
(> .handle_request() <);
} with {
if (error() != ~stop) {
$sys.log(traceback().tb_to_text());
.respond(traceback().tb_to_html(status = 500));
}
}
}
};
public method .log_request() {
var line;
line = strfmt("%s - - [%s] %s \"%s %s\" %s %s",
.host(), $time.format("%d %h %y %H:%M"),
method, URL, http, status, bytes);
if ((| header["User-Agent"] |))
line = line + ";; " + header["User-Agent"].join();
dblog("LOG: " + line);
.reset_variables();
};
public method .send_header() {
arg [fields];
var x;
cwrite(strings_to_buf([http + " " + tostr(status),
"Server: ColdWeb/3.1 (Customized: Server Purpose)",
"Content-type: " + ctype,
"Content-length: " + tostr(bytes),
@fields,
""]));
};
public method .redirect() {
arg url;
var body;
body = "<head><title>Redirected</title></head>\n<body>If you are reading this text, your client does not support redirection, and you should go to:<pre><a href=\"" + url + "\">" + url + "</a></pre></body>";
status = 302;
body = str_to_buf(body);
bytes = buflen(body);
.send_header("Location: " + url);
cwrite(body);
.close();
.log_request();
};
public method .respond() {
arg body, [close];
body = str_to_buf(body);
bytes = buflen(body);
.send_header();
cwrite(body);
.close(@close);
.log_request();
};
// Just diddle and get things to the point that .handle_request() likes it
public method .method_POST() {
var len, body, part;
len = (| header["Content-Length"] |);
if (len == ~keynf || !len)
(> .respond($sys.response(status = 400, "No Content-Length.")) <);
len = toint(len[1]);
// what a nightmare: sometimes the input for post will
// not be received immediately, so we wait for it.
if (buflen(buffer) < len) {
reading = 300;
while (buflen(buffer) < len && --reading) {
$sys.sleep();
refresh();
}
if (buflen(buffer) < len) {
buffer = `[];
status = 400;
.respond($sys.response(400, "Timeout on receiving POST request."));
return;
}
}
body = buf_to_strings(subbuf(buffer, 1, len));
buffer = subbuf(buffer, len + 1);
if ((`[13, 10] in buffer) == 1)
buffer = subbuf(buffer, 3);
if (body[listlen(body)] == `[])
body = delete(body, listlen(body));
else
body = replace(body, listlen(body), buf_to_str(body[listlen(body)]));
// handle this differently in normal situations
for part in (body)
info = info.add('args, info['args].union(part.break_fields()));
.handle_request();
};
private method .handle_request() {
var page, buf, path, obj;
// HERE: Insert handling routines, finish with a buffer 'buf'
status = 200;
buf = str_to_buf($sys.response(status, "You need to put something here."));
// send it off
bytes = buflen(buf);
.send_header();
cwrite(buf);
.close();
.log_request();
};