From abe0486678ec83caeb6f0ebf12e7d53266f43eff Mon Sep 17 00:00:00 2001
From: Willow Barraco <contact@willowbarraco.fr>
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