Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons
Commit info
ID
abe0486678ec83caeb6f0ebf12e7d53266f43eff
Author
Willow Barraco <contact@willowbarraco.fr>
Author date
Wed, 13 Mar 2024 16:10:34 +0100
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sun, 16 Mar 2025 03:00:23 +0800
Actions
add httpserv
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)!;
};
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);
};

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);
};