Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons
Commit info
ID
43e543f01792575a82f3c6fc4fdfd05a5dd826cc
Author
Willow Barraco <contact@willowbarraco.fr>
Author date
Thu, 28 Mar 2024 09:21:45 +0100
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sun, 16 Mar 2025 03:00:23 +0800
Actions
add httpclient
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);
};
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,
};
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);
};