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/client/http.ha (raw)

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