Lindenii Project Forge
Serve static files
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> // Adapted from template by Willow Barraco <contact@willowbarraco.fr>
use fs;
use getopt; use log; use net; use net::dial; use net::http; use net::ip; use net::tcp; use net::uri; use os; use memio; use io; use fmt; use bufio; const usage: [_]getopt::help = [ "Lindenii Forge Server", ('c', "config", "path to configuration file") ];
let static_fs: nullable *fs::fs = null;
export fn main() void = { 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 opt .. cmd.opts) { switch (opt.0) { case 'c' => yield; // TODO: actually handle the config case => abort("unreachable"); }; };
static_fs = os::diropen("static")!;
const server = match (http::listen(ip_addr, port, net::tcp::reuseport, net::tcp::reuseaddr)) { case let this: *http::server => yield this; case => 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 => log::println("failure while serving"); continue; }; defer http::serve_finish(serv_req); match (handlereq(serv_req.socket, &serv_req.request)) { case void => yield; case => log::println("error while handling request"); }; }; };
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> use fmt;
use fs;
use htmpl; use io; use net::http; use net::uri; use strconv; use strings;
fn handlereq(conn: io::handle, request: *http::request) (void | io::error | nomem) = {
fn handlereq(conn: io::handle, request: *http::request) (void | io::error | nomem | fs::error) = {
let segments = match(segments_from_path(request.target.raw_path)) { case let s: []str => yield s; case uri::invalid => start_response(conn, 400, "text/plain")?; fmt::fprintln(conn, "Invalid URI")?; return void; case nomem => return nomem; case => abort("unreachable"); }; defer strings::freeall(segments); let trailing_slash: bool = false; if (segments[len(segments) - 1] == "") { trailing_slash = true; free(segments[len(segments) - 1]); segments = segments[.. len(segments) - 1]; }; if (len(segments) == 0) { start_response(conn, 200, "text/html")?; return tp_index(conn); }; if (segments[0] == ":") { if (len(segments) == 1) { start_response(conn, 404, "text/plain")?; fmt::fprintln(conn, "Error: Blank system endpoint")?; return; }; switch (segments[1]) { case "static" => if (len(segments) == 2) { start_response(conn, 404, "text/plain")?; fmt::fprintln(conn, "Error: Blank static endpoint")?; return; };
let fs_segments = segments[2 ..]; for (let fs_segment .. fs_segments) { if (strings::contains(fs_segment, "/")) { start_response(conn, 400, "text/plain")?; fmt::fprintln(conn, "Error: Slash found in filesystem path")?; return; }; };
start_response(conn, 501, "text/plain")?; fmt::fprintln(conn, "Not implemented yet")?;
let fs_segment_path = strings::join("/", fs_segments...)?; defer free(fs_segment_path); let file = match (fs::open(static_fs as *fs::fs, fs_segment_path)) { case let f: io::handle => yield f; case fs::error => start_response(conn, 500, "text/plain")?; fmt::fprintln(conn, "Filesystem error")?; return; }; defer io::close(file)!; start_response(conn, 200, "text/css")?; io::copy(conn, file)?;
case => start_response(conn, 404, "text/plain")?; fmt::fprintln(conn, "Error: Unknown system endpoint")?; }; }; }; fn start_response(conn: io::handle, status: uint, content_type: str) (void | io::error | nomem) = { // TODO: add len and other headers fmt::fprint(conn, "HTTP/1.1 ")?; fmt::fprint(conn, strconv::utos(status))?; fmt::fprint(conn, " ")?; fmt::fprint(conn, http::status_reason(status))?; fmt::fprint(conn, "\r\n")?; fmt::fprint(conn, "Content-Type: ")?; fmt::fprint(conn, content_type)?; fmt::fprint(conn, "\r\n")?; fmt::fprint(conn, "\r\n")?; };