From bcdee78c0f9d365811a4abfd1f7d3b3f0d14aeb5 Mon Sep 17 00:00:00 2001 From: Willow Barraco Date: Thu, 28 Mar 2024 09:21:45 +0100 Subject: [PATCH] add httpclient --- cmd/httpclient/main.ha | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ev/client/client.ha | 15 +++++++++++++++ ev/client/http.ha | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/cmd/httpclient/main.ha b/cmd/httpclient/main.ha new file mode 100644 index 0000000000000000000000000000000000000000..b5e6528347e821ea1cb635ba304bca104ad5571e --- /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 0000000000000000000000000000000000000000..8348532b83f1639858d622c3b7ad9e3d12be92d8 --- /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 0000000000000000000000000000000000000000..6e1d4d33bd17b3fe067e5de36f05c9002469e970 --- /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.48.1