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; const usage: [_]getopt::help = [ "HTTP server", ('a', "address", "listened address (ex: 127.0.0.1:8080)") ]; 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 => 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)!; 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 => handle_notfound(&buf, serv_req); return; }; case "OPTIONS" => handle_cors(&buf, serv_req); return; case => handle_notfound(&buf, serv_req); return; }; }; export fn handle_file(buf: *memio::stream, serv_req: *http::server_request) void = { let request = &serv_req.request; fmt::printfln("handling file for path: {}", request.target.path)!; let pathbuf = strings::toutf8(request.target.path); if (bytes::contains(pathbuf[1..], '/')) { fmt::printfln("additional slash. not handling that.")!; handle_notfound(buf, serv_req); return; }; let p = path::init(request.target.path)!; let ext = path::peek_ext(&p); if (ext is void) { handle_notfound(buf, serv_req); return; }; 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 => handle_notfound(buf, serv_req); return; }; let filename = path::basename(request.target.path); let fp = os::open(filename)!; 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) )!; }; export fn handle_notfound(buf: *io::stream, request: *http::server_request) void = { fmt::fprintfln(buf, "not found")!; http::response_write( request.socket, http::STATUS_NOT_FOUND, buf, ("Content-Type", "text/plain") )!; }; export fn handle_cors(buf: *io::stream, 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") )!; }; export fn handle_exec(buf: *io::stream, serv_req: *http::server_request) void = { let request = serv_req.request; let payload = match (request.body) { case void => http::response_write( serv_req.socket, http::STATUS_BAD_REQUEST, void, ("Content-Length", "0") )!; return; case let body: io::handle => let payload = json::load(body)!; // defer json::finish(payload); yield payload; }; defer json::finish(payload); let payload = match (payload) { case let obj: json::object => yield obj; case => handle_notfound(buf, serv_req); return; }; let files = match (json::get(&payload, "files")) { case void => handle_notfound(buf, serv_req); return; case let j: *json::value => yield j; }; let files = match (*files) { case let o: json::object => yield o; case => handle_notfound(buf, serv_req); return; }; let code = match (json::get(&files, "")) { case void => handle_notfound(buf, serv_req); return; case let j: *json::value => yield j; }; let code = match (*code) { case let c: str => yield c; case => handle_notfound(buf, serv_req); return; }; 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::set(&obj, "id", "123"); json::set(&obj, "ok", true); json::set(&obj, "duration", 0.0); 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") )!; }; export fn handle_index(buf: *io::stream, serv_req: *http::server_request) void = { let request = serv_req.request; fmt::fprintfln(buf, "Method: {}", request.method)!; fmt::fprintfln(buf, "Path: {}", request.target.path)!; fmt::fprintfln(buf, "Fragment: {}", request.target.fragment)!; fmt::fprintfln(buf, "Query: {}", request.target.query)!; fmt::fprintfln(buf, "Headers: < void; case let body: io::handle => fmt::fprintfln(buf, "Body: < fmt::fprintln(buf, strings::fromutf8(line)!)!; break; case io::EOF => break; }; }; fmt::fprintfln(buf, "EOF")!; }; http::response_write( serv_req.socket, http::STATUS_OK, buf, ("Content-Type", "text/plain") )!; };