// ------------------------------------------------------------------ // // 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(); };