~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2

[PATCH hare-ev 1/3] fix: typo

Details
Message ID
<20240419115613.1404-4-contact@willowbarraco.fr>
DKIM signature
pass
Download raw message
Patch: +2 -2
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
 ev/dial/ip.ha | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ev/dial/ip.ha b/ev/dial/ip.ha
index ce718e7..e8a3b6b 100644
--- a/ev/dial/ip.ha
+++ b/ev/dial/ip.ha
@@ -90,7 +90,7 @@ fn dial_tcp_connectcb(
};

fn dial_tcp_cancel(req: *ev::req) void = {
	let state = req: *tcp_dialer;
	let state = req.user: *tcp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
@@ -165,7 +165,7 @@ fn dial_udp_connect(state: *udp_dialer) void = {
};

fn dial_udp_cancel(req: *ev::req) void = {
	let state = req: *udp_dialer;
	let state = req.user: *udp_dialer;
	ev::cancel(&state.req);
	free(state.ip);
	free(state);
-- 
2.44.0

[PATCH hare-ev 2/3] add httpserv

Details
Message ID
<20240419115613.1404-5-contact@willowbarraco.fr>
In-Reply-To
<20240419115613.1404-4-contact@willowbarraco.fr> (view parent)
DKIM signature
pass
Download raw message
Patch: +269 -0
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
 cmd/httpserv/main.ha |  83 ++++++++++++++++++++++++++++
 ev/server/http.ha    | 125 +++++++++++++++++++++++++++++++++++++++++++
 ev/server/server.ha  |  61 +++++++++++++++++++++
 3 files changed, 269 insertions(+)
 create mode 100644 cmd/httpserv/main.ha
 create mode 100644 ev/server/http.ha
 create mode 100644 ev/server/server.ha

diff --git a/cmd/httpserv/main.ha b/cmd/httpserv/main.ha
new file mode 100644
index 0000000..6633dbc
--- /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 0000000..c39f4bd
--- /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 0000000..2d5c6ab
--- /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.44.0

[PATCH hare-ev 3/3] add httpclient

Details
Message ID
<20240419115613.1404-6-contact@willowbarraco.fr>
In-Reply-To
<20240419115613.1404-4-contact@willowbarraco.fr> (view parent)
DKIM signature
pass
Download raw message
Patch: +258 -0
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
 cmd/httpclient/main.ha |  90 ++++++++++++++++++++++++
 ev/client/client.ha    |  15 ++++
 ev/client/http.ha      | 153 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 258 insertions(+)
 create mode 100644 cmd/httpclient/main.ha
 create mode 100644 ev/client/client.ha
 create mode 100644 ev/client/http.ha

diff --git a/cmd/httpclient/main.ha b/cmd/httpclient/main.ha
new file mode 100644
index 0000000..b5e6528
--- /dev/null
+++ b/cmd/httpclient/main.ha
@@ -0,0 +1,90 @@
use ev;
use ev::client;
use net::uri;
use net::http;
use ev::dial;
use net;
use io;
use errors;
use log;
use os;
use fmt;

type state = struct {
	loop: *ev::loop,
	client: *http::client,
	exit: int,
};

export fn main() void = {
	const loop = ev::newloop()!;
	defer ev::finish(&loop);

	const client = http::newclient("Hare net::http test client");
	defer http::client_finish(&client);

	const targ = uri::parse("http://127.0.0.1:8080")!;
	defer uri::finish(&targ);

	let req = http::new_request(&client, "GET", &targ)!;

	let state = state {
		loop = &loop,
		client = &client,
		...
	};

	const con = match (client::http_do(&loop, &client, &req, &http_done, &state)) {
	case let req: ev::req => yield req;
	case let err: dial::error =>
		log::println("dial error:", dial::strerror(err));
		return;
	case let err: io::error =>
		log::println("io error:", io::strerror(err));
		return;
	};

	for (ev::dispatch(&loop, -1)!) void;
};

fn http_done(
	user: nullable *opaque,
	r: (
		(*ev::file, http::response) |
		io::EOF | dial::error | io::error | http::protoerr | errors::unsupported
	)
) void = {
	const state = user: *state;

	const (file, resp) = match (r) {
	case let resp: (*ev::file, http::response) => yield resp;
	case io::EOF =>
		log::println("connection closed");
		ev::stop(state.loop);
		return;
	case let err: dial::error =>
		log::println("dial error", dial::strerror(err));
		ev::stop(state.loop);
		return;
	case let err: io::error =>
		log::println("io error:", io::strerror(err));
		ev::stop(state.loop);
		return;
	case let err: http::protoerr =>
		log::println("http protoerror:", http::strerror(err));
		ev::stop(state.loop);
		return;
	case let err: errors::unsupported =>
		log::println("errors:", errors::strerror(err));
		ev::stop(state.loop);
		return;
	};

	fmt::printfln("Headers:")!;
	http::write_header(os::stdout, &resp.header)!;
	fmt::println()!;
	io::copy(os::stdout, resp.body)!;

	ev::close(file);
	ev::stop(state.loop);
};
diff --git a/ev/client/client.ha b/ev/client/client.ha
new file mode 100644
index 0000000..8348532
--- /dev/null
+++ b/ev/client/client.ha
@@ -0,0 +1,15 @@
use ev;
use io;
use memio;
use os;

type client = struct {
	req: ev::req,
	sock: nullable *ev::file,
	cb: *opaque,
	user: nullable *opaque,

	wbuf: []u8,
	rbuf: [os::BUFSZ]u8,
	buf: memio::stream,
};
diff --git a/ev/client/http.ha b/ev/client/http.ha
new file mode 100644
index 0000000..6e1d4d3
--- /dev/null
+++ b/ev/client/http.ha
@@ -0,0 +1,153 @@
use bufio;
use errors;
use ev::dial;
use ev;
use io;
use memio;
use net::http;
use os;
use types;

// A callback for an [[http_do]] operation.
export type http_donecb = fn(
	user: nullable *opaque,
	r: (
		(*ev::file, http::response) |
		dial::error | io::EOF | io::error | http::protoerr | errors::unsupported
	)
) void;

export fn http_do(
	loop: *ev::loop,
	reqcli: *http::client,
	req: *http::request,
	cb: *http_donecb,
	user: nullable *opaque
) (ev::req | dial::error | io::error) = {
	const cli = alloc(client {
		cb = cb,
		user = user,
		buf = memio::dynamic(),
		...
	});
	http::request_write_internal(&cli.buf, req, reqcli)?;
	const req = dial::dial_uri(
		loop,
		"tcp",
		req.target,
		&http_dialed,
		cli,
	)?;
	cli.req = req;
	return ev::mkreq(&http_do_cancel, cli);
};

export fn http_send(
	file: *ev::file,
	reqcli: *http::client,
	req: *http::request,
	cb: *http_donecb,
) (void | io::error) = {
	const cli = ev::getuser(file): *client;

	http::request_write_internal(&cli.buf, req, reqcli)?;
	cli.wbuf = memio::buffer(&cli.buf: *memio::stream);
	ev::write(file, &http_write, cli.wbuf);
};

fn http_do_cancel(req: *ev::req) void = {
	let cli = req.user: *client;
	ev::cancel(&cli.req);
	client_close(cli);
};

fn http_dialed(user: nullable *opaque, r: (*ev::file | dial::error)) void = {
	const cli = user: *client;
	cli.req = ev::req { ... };

	const file = match (r) {
	case let file: *ev::file => yield file;
	case let err: dial::error =>
		const cb = cli.cb: *http_donecb;
		cb(cli.user, err);
		client_close(cli);
		return;
	};

	ev::setuser(file, cli);

	cli.wbuf = memio::buffer(&cli.buf: *memio::stream);
	ev::write(file, &http_write, cli.wbuf);
};

fn http_write(sock: *ev::file, r: (size | io::error)) void = {
	const cli = ev::getuser(sock): *client;

	const n = match (r) {
	case let err: io::error =>
		const cb = cli.cb: *http_donecb;
		cb(cli.user, err);
		client_close(cli);
		return;
	case let n: size =>
		yield n;
	};
	static delete(cli.wbuf[..n]);
	if (len(cli.wbuf) != 0) {
		ev::write(sock, &http_write, cli.wbuf);
		return;
	};

	memio::reset(&cli.buf);
	ev::read(sock, &http_read, cli.rbuf);
};

fn http_read(sock: *ev::file, r: (size | io::EOF | io::error)) void = {
	const cli = ev::getuser(sock): *client;
	const cb = cli.cb: *http_donecb;

	const n = match (r) {
	case let err: io::error =>
		cb(cli.user, err);
		client_close(cli);
		return;
	case io::EOF =>
		cb(cli.user, io::EOF);
		client_close(cli);
		return;
	case let n: size =>
		yield n;
	};

	io::seek(&cli.buf, 0, io::whence::END)!;
	io::write(&cli.buf, cli.rbuf[..n])!;
	io::seek(&cli.buf, 0, io::whence::SET)!;

	let scan = bufio::newscanner(&cli.buf, types::SIZE_MAX);
	defer bufio::finish(&scan);

	let resp = match (http::response_scan(&scan)) {
	case let resp: http::response => yield resp;
	case io::EOF =>
		ev::read(sock, &http_read, cli.rbuf);
		return;
	case let err: (http::protoerr | errors::unsupported | io::error) =>
		cb(cli.user, err);
		client_close(cli);
		return;
	};
	defer http::parsed_response_finish(&resp);

	memio::reset(&cli.buf);

	cb(cli.user, (sock, resp));

};

fn client_close(cli: *client) void = {
	if (!(cli.sock is null)) {
		ev::close(cli.sock as *ev::file);
	};
	io::close(&cli.buf)!;
	free(cli);
};
-- 
2.44.0
Reply to thread Export thread (mbox)