use bufio; use bytes; use encoding::json; use fmt; use getopt; use io; use log; use log::logfmt; use memio; use net; use net::dial; use net::http; use net::ip; use net::tcp::{reuseaddr}; use net::uri; use os; use path; use strings; use thread; use time; type notfound = !void; type badrequest = !void; type http_err = !(notfound | badrequest); const usage: [_]getopt::help = [ "HTTP server", ('a', "address", "listened address (ex: 127.0.0.1:8080)"), ('b', "path", "Set the public base path. Default: ."), ]; let public_base_path = "."; export fn main() void = { let l = logfmt::new(os::stdout); log::setlogger(&l); const cmd = getopt::parse(os::args, usage...); defer getopt::finish(&cmd); let port: u16 = 8080; let ip_addr: ip::addr4 = [127, 0, 0, 1]; for (let i = 0z; i < len(cmd.opts); i += 1) { const opt = cmd.opts[i]; switch (opt.0) { case 'a' => match (dial::splitaddr(opt.1, "")) { case let value: (str, u16) => ip_addr = ip::parsev4(value.0)!; port = value.1; case dial::invalid_address => abort("Invalid address"); }; case 'b' => public_base_path = strings::dup(opt.1); case => abort(); // unreachable }; }; const server = match (http::listen(ip_addr, port, reuseaddr)) { case let this: *http::server => yield this; case net::error => abort("failure while listening"); }; defer http::server_finish(server); for (true) { const serv_req = match (http::serve(server)) { case let this: *http::server_request => yield this; case net::error => abort("failure while serving"); }; log::println("method", serv_req.request.method, "uri", uri::string(serv_req.request.target)); let tid = thread::spawn(&handle_req, serv_req)!; thread::detach(tid)!; }; }; fn handle_req(arg: nullable *opaque) void = { let serv_req = arg: *http::server_request; defer http::serve_finish(serv_req); let buf = memio::dynamic(); defer io::close(&buf)!; match (route_request(&buf, serv_req)) { case void => void; case badrequest => handle_badrequest(serv_req); case notfound => handle_notfound(&buf, serv_req); }; }; fn route_request(buf: *io::stream, serv_req: *http::server_request) (void | http_err) = { let request = &serv_req.request; switch (request.method) { case "GET" => switch (request.target.path) { case "/" => handle_index(buf, serv_req); case => handle_file(buf, serv_req)?; return; }; case "POST" => switch (request.target.path) { case "/v1/exec" => handle_exec(buf, serv_req)?; case => return notfound; }; case "OPTIONS" => handle_cors(serv_req); return; case => return notfound; }; }; fn handle_index(buf: *io::stream, serv_req: *http::server_request) void = { let request = serv_req.request; let path = fmt::asprintf("{}/index.html", public_base_path); defer free(path); let fp = os::open(path)!; defer io::close(fp)!; let filecontent = io::drain(fp)!; defer free(filecontent); io::writeall(buf, filecontent)!; http::response_write( serv_req.socket, http::STATUS_OK, buf, ("Content-Type", "text/html; charset=utf-8") )!; }; fn handle_file(buf: *io::stream, serv_req: *http::server_request) (void | notfound) = { let request = &serv_req.request; let pathbuf = strings::toutf8(request.target.path); if (bytes::contains(pathbuf[1..], '/')) { return notfound; }; let p = path::init(request.target.path)!; let ext = path::peek_ext(&p); if (ext is void) { return notfound; }; let content_type = switch (ext: str) { case "html" => yield "text/html; charset=utf-8"; case "css" => yield "text/css"; case "js" => yield "application/javascript; charset=utf-8"; case => return notfound; }; let filename = path::basename(request.target.path); let path = fmt::asprintf("{}/{}", public_base_path, filename); defer free(path); let fp = match (os::open(path)) { case let fp: io::file => yield fp; case => return notfound; }; defer io::close(fp)!; let filecontent = io::drain(fp)!; defer free(filecontent); io::writeall(buf, filecontent)!; http::response_write( serv_req.socket, http::STATUS_OK, buf, ("Content-Type", content_type) )!; }; fn handle_notfound(buf: *io::stream, request: *http::server_request) void = { http::response_write( request.socket, http::STATUS_NOT_FOUND, buf, ("Content-Type", "text/plain") )!; }; fn handle_badrequest(request: *http::server_request) void = { http::response_write( request.socket, http::STATUS_BAD_REQUEST, void, ("Content-Length", "0") )!; }; fn handle_cors(serv_req: *http::server_request) void = { let request = serv_req.request; if (request.target.path == "/v1/exec") { http::response_write( serv_req.socket, http::STATUS_OK, void, ("Content-Length", "0"), ("access-control-allow-origin", "*"), ("access-control-allow-methods", "options, post"), ("access-control-allow-headers", "authorization, content-type") )!; return; }; http::response_write( serv_req.socket, http::STATUS_METHOD_NOT_ALLOWED, void, ("Content-Length", "0") )!; }; fn handle_exec(buf: *io::stream, serv_req: *http::server_request) (void | badrequest) = { let request = serv_req.request; let payload = match (request.body) { case void => return badrequest; case let body: io::handle => let payload = match (json::load(body)) { case let j: json::value => yield j; case let err: json::error => return badrequest; }; yield payload; }; defer json::finish(payload); let payload = match (payload) { case let obj: json::object => yield obj; case => return badrequest; }; let code = match (json::get(&payload, "code")) { case let payload: *json::value => yield payload; case void => return badrequest; }; let code = match (*code) { case let c: str => yield c; case => return badrequest; }; let (stdout, stderr) = run_code(code); defer free(stderr); defer free(stdout); let obj = json::newobject(); defer json::finish(obj); json::set(&obj, "stdout", stdout); json::set(&obj, "stderr", stderr); json::dump(buf, obj)!; http::response_write( serv_req.socket, http::STATUS_OK, buf, ("Content-Type", "application/json"), ("access-control-allow-origin", "*"), ("access-control-allow-methods", "options, post"), ("access-control-allow-headers", "authorization, content-type") )!; };