From 7b7ca5ce3ad0a9025da0751f229e23c9cd062ea7 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Wed, 17 Sep 2025 08:58:13 +0800 Subject: [PATCH] Use the ev::-based HTTP server --- example/main.ha | 206 ++++++++++++++++++++++++++++++++++++++++++++--------- diff --git a/example/main.ha b/example/main.ha index bfaf7b4825b06dd7d8e6ada539585e44d59db248..9494c515fc5203d3c952517d874c31383217f277 100644 --- a/example/main.ha +++ b/example/main.ha @@ -2,23 +2,37 @@ // SPDX-License-Identifier: CC0-1.0 // By Runxi Yu // Adapted from template by Willow Barraco +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 = { @@ -26,46 +40,170 @@ 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); }; -- 2.48.1