From a9c6894aa7088882c99833406516dcfc4c34751d Mon Sep 17 00:00:00 2001 From: Willow Barraco Date: Wed, 13 Mar 2024 16:10:34 +0100 Subject: [PATCH] add httpserv --- cmd/httpserv/main.ha | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ev/server/http.ha | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ev/server/server.ha | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/cmd/httpserv/main.ha b/cmd/httpserv/main.ha new file mode 100644 index 0000000000000000000000000000000000000000..6633dbc44ec242423c3fb036f4253490322e622d --- /dev/null +++ b/cmd/httpserv/main.ha @@ -0,0 +1,83 @@ +use errors; +use ev; +use ev::server; +use fmt; +use io; +use log; +use memio; +use net::http; +use net::ip; +use net::tcp; +use net; +use os; +use unix::signal; + +type state = struct { + loop: *ev::loop, + exit: int, +}; + +export fn main() void = { + const loop = ev::newloop()!; + defer ev::finish(&loop); + + const sock = match (ev::listen_tcp(&loop, ip::LOCAL_V4, 8080, 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 sock: *ev::file => + yield sock; + }; + defer ev::close(sock); + + let state = state { + loop = &loop, + ... + }; + const server = server::http_serve(sock, &http_serve, &state); + defer server::server_finish(server); + + const sig = ev::signal(&loop, &signal, signal::sig::INT, signal::sig::TERM)!; + defer ev::close(sig); + ev::setuser(sig, &state); + + log::println("Listening on 127.0.0.1:8080"); + for (ev::dispatch(&loop, -1)!) void; + os::exit(state.exit); +}; + +fn signal(file: *ev::file, sig: signal::sig) void = { + log::printfln("Exiting due to {}", signal::signame(sig)); + const state = ev::getuser(file): *state; + ev::stop(state.loop); +}; + +fn http_serve( + user: nullable *opaque, + req: http::request, + rw: http::response_writer +) void = { + const state = user: *state; + + const (ip, port) = http::peeraddr(&rw); + log::printfln("Serving from {}:{}", ip::string(ip), port); + + const buf = memio::dynamic(); + defer io::close(&buf)!; + + fmt::fprintfln(&buf, "Method: {}", req.method)!; + fmt::fprintfln(&buf, "Path: {}", req.target.path)!; + fmt::fprintfln(&buf, "Fragment: {}", req.target.fragment)!; + fmt::fprintfln(&buf, "Query: {}", req.target.query)!; + fmt::fprintfln(&buf, "Headers:")!; + http::write_header(&buf, &req.header)!; + fmt::fprintln(&buf)!; + io::copy(&buf, req.body)!; + + io::seek(&buf, 0, io::whence::SET)!; + + http::response_add_header(&rw, "Content-Type", "text/plain"); + http::response_set_body(&rw, &buf); + http::response_write(&rw)!; +}; diff --git a/ev/server/http.ha b/ev/server/http.ha new file mode 100644 index 0000000000000000000000000000000000000000..c39f4bdee8f9b170218f88d29cb8637e46f4294f --- /dev/null +++ b/ev/server/http.ha @@ -0,0 +1,125 @@ +use ev; +use bufio; +use errors; +use io; +use memio; +use net::http; +use net::ip; +use net::tcp; +use net; +use strings; +use types; + +// A callback for an [[http_serve]] operation. +export type http_servecb = fn(user: nullable *opaque, request: http::request, rw: http::response_writer) void; + +// Schedules an http serve operation on a socket. The user must free resources +// when done with [[server_finish]] +export fn http_serve( + sock: *ev::file, + cb: *http_servecb, + user: nullable *opaque, +) *server = { + const serv = newserver(sock, cb, user); + ev::setuser(sock, serv); + ev::accept(sock, &http_accept); + return serv; +}; + +fn http_accept(sock: *ev::file, r: (*ev::file | net::error)) void = { + const server = ev::getuser(sock): *server; + + const sock = match (r) { + case let sock: *ev::file => + yield sock; + case let err: net::error => + // TODO handle it + return; + }; + + const client = newclient( + server, + sock, + ); + ev::setuser(client.sock, client); + ev::read(client.sock, &http_read, client.rbuf); + + append(server.clients, client); + + ev::accept(server.sock, &http_accept); +}; + +fn http_read(sock: *ev::file, r: (size | io::EOF | io::error)) void = { + const client = ev::getuser(sock): *server_client; + + const n = match (r) { + case let err: io::error => + client_close(client); + return; + case io::EOF => + client_close(client); + return; + case let n: size => + yield n; + }; + + io::seek(&client.buf, 0, io::whence::END)!; + io::write(&client.buf, client.rbuf[..n])!; + io::seek(&client.buf, 0, io::whence::SET)!; + + let scan = bufio::newscanner(&client.buf, types::SIZE_MAX); + defer bufio::finish(&scan); + + let req = match (http::request_scan(&scan)) { + case let req: http::request => yield req; + case io::EOF => + ev::read(client.sock, &http_read, client.rbuf); + return; + case let err: (http::protoerr | errors::unsupported | io::error) => + client_close(client); + return; + }; + defer http::parsed_request_finish(&req); + + memio::reset(&client.buf); + + const resp = http::response { + version = (1, 1), + status = http::STATUS_OK, + reason = strings::dup(http::status_reason(http::STATUS_OK)), + header = [], + body = io::empty, + }; + defer io::close(resp.body)!; + + const cb = client.server.cb: *http_servecb; + cb(client.server.user, req, http::response_writer { + sock = ev::getfd(client.sock), + resp = resp, + writeto = &client.buf, + }); + + client.wbuf = memio::buffer(&client.buf); + ev::write(client.sock, &http_write, client.wbuf); +}; + +fn http_write(sock: *ev::file, r: (size | io::error)) void = { + const client = ev::getuser(sock): *server_client; + + const n = match (r) { + case let err: io::error => + client_close(client); + return; + case let n: size => + yield n; + }; + static delete(client.wbuf[..n]); + if (len(client.wbuf) != 0) { + ev::write(client.sock, &http_write, client.wbuf); + return; + }; + + memio::reset(&client.buf); + ev::read(client.sock, &http_read, client.rbuf); +}; + diff --git a/ev/server/server.ha b/ev/server/server.ha new file mode 100644 index 0000000000000000000000000000000000000000..2d5c6abbce357b17374bf5daa878b5dcbb412455 --- /dev/null +++ b/ev/server/server.ha @@ -0,0 +1,61 @@ +use ev; +use io; +use memio; +use os; + +export type server = struct { + sock: *ev::file, + clients: []*server_client, + cb: *opaque, + user: nullable *opaque +}; + +export type server_client = struct { + sock: *ev::file, + server: *server, + + rbuf: [os::BUFSZ]u8, + wbuf: []u8, + buf: memio::stream, +}; + +export fn newserver(sock: *ev::file, cb: *opaque, user: nullable *opaque) *server = { + return alloc(server { + sock = sock, + cb = cb, + user = user, + ... + }); +}; + +export fn server_finish(serv: *server) void = { + for (let i = 0z; i < len(serv.clients); i += 1) { + client_close(serv.clients[i]); + i -= 1; + }; + free(serv); +}; + +export fn newclient(serv: *server, sock: *ev::file) *server_client = { + return alloc(server_client { + server = serv, + sock = sock, + buf = memio::dynamic(), + ... + }); +}; + +export fn client_close(client: *server_client) void = { + const server = client.server; + for (let i = 0z; i < len(server.clients); i += 1) { + if (server.clients[i] == client) { + delete(server.clients[i]); + break; + }; + }; + ev::close(client.sock); + + io::close(&client.buf)!; + + free(client); +}; -- 2.48.1