Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons

Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.

/ev/server/http.ha (raw)

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 | nomem) = {
	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 | nomem) = {
	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 | nomem) = {
	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 | nomem) = {
	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);
};