use getopt; use net; use net::ip; use net::uri; use net::http; use net::dial; use net::tcp::{reuseaddr}; use os; use memio; use io; use fmt; use bufio; use strings; use thread; use time; use log::logfmt; use log; use encoding::json; 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; let route = (request.method, request.target.path); switch (request.method) { case "GET" => switch (request.target.path) { case "/" => handle_index(&buf, &serv_req.request); case => handle_notfound(&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; }; http::response_write( serv_req.socket, http::STATUS_OK, &buf, ("Content-Type", "text/plain") )!; }; 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)!; yield payload; }; json::dump(buf, payload)!; 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, request: *http::request) void = { 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")!; }; };