Lindenii Project Forge
Use the ev::-based HTTP server
// SPDX-License-Identifier: CC0-1.0 // By Runxi Yu <https://runxiyu.org> // Adapted from template by Willow Barraco <contact@willowbarraco.fr>
use errors; use ev; use evhttp = ev::http;
use getopt;
use htmpl;
use fmt; use io;
use log;
use memio;
use net;
use net::ip;
use net::dial;
use net::http;
use net::dial;
use net::ip; use net::tcp;
use os;
use memio; use io; use fmt; use bufio; use strings;
use unix::signal; type state = struct { loop: *ev::loop, accept: ev::sockreq, clients: []*client, }; type client = struct { state: *state, sock: *ev::file, httpreq: evhttp::httpreq, };
const usage: [_]getopt::help = [ "HTTP server",
('a', "address", "listened address")
('a', "address", "listened address (ex: 127.0.0.1:8080)"),
]; 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];
let listen_ip: ip::addr = ip::LOCAL_V4;
for (let opt .. cmd.opts) { 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 let tup: (str, u16) => const v4 = ip::parsev4(tup.0)!; listen_ip = v4: ip::addr; port = tup.1;
case dial::invalid_address =>
abort("invalid address");
log::fatalf("invalid address: {}", opt.1);
};
case => abort(); // unreachable
case => abort("unreachable getopt value");
}; };
const server = match (http::listen(ip_addr, port)) { case let this: *http::server => yield this; case net::error => abort("failure while listening");
const loop = ev::newloop()!; defer ev::finish(&loop); const sock = match (ev::listen_tcp(&loop, listen_ip, port, tcp::reuseaddr)) { case let err: net::error => log::fatalf("Error: listen: {}", net::strerror(err)); case let err: errors::error => log::fatalf("Error: listen: {}", errors::strerror(err)); case let s: *ev::file => yield s;
};
defer http::server_finish(server);
defer ev::close(sock); let st = state { loop = &loop, accept = ev::accept_init(sock), clients = [], }; defer { free(st.clients); }; ev::req_setuser(&st.accept, &st); ev::accept(&st.accept, &on_accept); const sig = ev::signal_init(&loop, &st, signal::sig::INT, signal::sig::TERM)!; defer ev::signal_finish(&sig); ev::handle(&sig, &on_signal); log::printfln("Listening on {}:{}", ip::string(listen_ip), port); ev::run(&loop)!; }; fn on_signal(req: *ev::signal, sig: signal::sig, user: nullable *opaque) void = { const st = user: *state; ev::cancel(&st.accept);
for (true) { const serv_req = match (http::serve(server)) { case let this: *http::server_request => yield this; case net::error => abort("failure while serving"); }; defer http::serve_finish(serv_req);
for (0 != len(st.clients)) { client_destroy(st.clients[0]); }; };
match (handlereq(serv_req.socket, &serv_req.request)) { case void => yield; case io::error => log::println("error while handling request");
fn on_accept(req: *ev::sockreq, r: (*ev::file | net::error), user: nullable *opaque) void = { const st = user: *state; const s = match (r) { case let f: *ev::file => yield f; case let err: net::error => log::println("accept net error:", net::strerror(err)); ev::accept(req, &on_accept); return; }; const cl = alloc(client { state = st, sock = s, httpreq = evhttp::httpreq_init(s), })!; ev::req_setuser(&cl.httpreq, cl); append(st.clients, cl)!; evhttp::request_read(&cl.httpreq, &on_read); ev::accept(req, &on_accept); }; fn client_destroy(cl: *client) void = { const st = cl.state; for (let i = 0z; i < len(st.clients); i += 1) { if (st.clients[i] == cl) { delete(st.clients[i]); break;
}; };
evhttp::httpreq_finish(&cl.httpreq); ev::close(cl.sock); free(cl);
};
export fn handlereq(conn: io::handle, request: *http::request) (void | io::error | nomem) = { htmpl::write(conn, "HTTP/1.1 200 OK\r\n")?; htmpl::write(conn, "Content-Type: text/html\r\n\r\n")?; tp_index(conn)?;
fn on_read( req: *evhttp::httpreq, r: (http::request | io::EOF | http::protoerr | io::error), user: nullable *opaque, ) void = { const cl = user: *client; const hreq = match (r) { case let q: http::request => yield q; case io::EOF => client_destroy(cl); return; case let err: http::protoerr => log::println("http proto error"); client_destroy(cl); return; case let err: io::error => log::println("http io error:", io::strerror(err)); client_destroy(cl); return; }; const file = ev::getfd(cl.sock); const (rip, rport) = tcp::peeraddr(file) as (ip::addr, u16); log::printfln("{}:{}: {} {}", ip::string(rip), rport, hreq.method, hreq.target.path); const resp = http::response { version = (1, 1), status = http::STATUS_OK, reason = "OK", body = &memio::dynamic(), ... }; defer { http::header_finish(&resp.header); io::close(resp.body)!; }; http::header_add(&resp.header, "Content-Type", "text/html")!; tp_index(resp.body)!; io::seek(resp.body, 0, io::whence::SET)!; evhttp::response_write(&cl.httpreq, &resp, &on_write)!; }; fn on_write( req: *evhttp::httpreq, r: (void | http::protoerr | io::error), user: nullable *opaque, ) void = { const cl = user: *client; match (r) { case void => void; case let err: http::protoerr => log::println("write proto error"); case let err: io::error => log::println("write io error:", io::strerror(err)); }; client_destroy(cl);
};