1
Fork 0
hare-http/net/http/client.ha
2023-10-07 12:14:26 +02:00

163 lines
4.5 KiB
Hare

use fmt;
use io;
use net::ip;
use net::uri;
use strconv;
use strings;
export type client = struct {
default_header: header,
default_transport: transport,
};
// Creates a new HTTP [[client]] with the provided User-Agent string.
//
// The HTTP client implements a number of sane defaults, which may be tuned. The
// set of default headers is configured with [[client_default_header]], and the
// default transport behavior with [[client_default_transport]].
//
// TODO: Implement and document the connection pool
//
// The caller must pass the client object to [[client_finish]] to free resources
// associated with this client after use.
export fn newclient(ua: str) client = {
let client = client { ... };
header_add(&client, "User-Agent", ua);
return client;
};
// Frees resources associated with an HTTP [[client]].
export fn client_finish(client: *client) void = {
header_free(&client.default_header);
};
// Returns the default headers used by this HTTP client, so that the user can
// examine or modify the net::http defaults (such as User-Agent or
// Accept-Encoding), or add their own.
export fn client_default_header(client: *client) *header = {
return &client.default_header;
};
// Returns the default [[transport]] configuration used by this HTTP client.
export fn client_default_transport(client: *client) *transport = {
return &client.default_transport;
};
fn new_request(client: *client, method: str, target: *uri::uri) request = {
let req = request {
method = method,
target = alloc(uri::dup(target)),
header = header_dup(&client.default_header),
transport = null,
body = void,
};
if (req.target.port == 0) {
switch (req.target.scheme) {
case "http" =>
req.target.port = 80;
case "https" =>
req.target.port = 443;
case => abort("net::http: unsupported URL scheme");
};
};
let host = match (req.target.host) {
case let host: str =>
yield host;
case let ip: ip::addr4 =>
yield ip::string(ip);
case let ip: ip::addr6 =>
static let buf: [64 + 2]u8 = [0...];
yield fmt::bsprintf(buf, "[{}]", ip::string(ip));
};
if (req.target.scheme == "http" && req.target.port != 80) {
host = fmt::asprintf("{}:{}", host, req.target.port);
} else if (target.scheme == "https" && target.port != 443) {
host = fmt::asprintf("{}:{}", host, req.target.port);
} else {
host = strings::dup(host);
};
defer free(host);
header_add(&req.header, "Host", host);
return req;
};
fn new_request_body(
client: *client,
method: str,
target: *uri::uri,
body: io::handle,
) request = {
let req = new_request(client, method, target);
req.body = body;
const offs = match (io::seek(body, 0, io::whence::CUR)) {
case let off: io::off =>
yield off;
case io::error =>
header_add(&req.header, "Transfer-Encoding", "chunked");
return req;
};
const ln = io::seek(body, 0, io::whence::END)!;
io::seek(body, offs, io::whence::SET)!;
header_add(&req.header, "Content-Length", strconv::ztos(ln: size));
return req;
};
fn uri_origin_form(target: *uri::uri) uri::uri = {
let target = *target;
target.scheme = "";
target.host = "";
target.fragment = "";
target.userinfo = "";
target.port = 0;
if (target.path == "") {
target.path = "/";
};
return target;
};
// Performs a synchronous HTTP GET request with the given client.
export fn get(client: *client, target: *uri::uri) (response | error) = {
const req = new_request(client, GET, target);
defer request_finish(&req);
return do(client, &req);
};
// Performs a synchronous HTTP HEAD request with the given client.
export fn head(client: *client, target: *uri::uri) (response | error) = {
const req = new_request(client, HEAD, target);
defer request_finish(&req);
return do(client, &req);
};
// Performs a synchronous HTTP POST request with the given client.
//
// If the provided I/O handle is seekable, the Content-Length header is added
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
export fn post(
client: *client,
target: *uri::uri,
body: io::handle,
) (response | error) = {
const req = new_request_body(client, POST, target, body);
defer request_finish(&req);
return do(client, &req);
};
// Performs a synchronous HTTP PUT request with the given client.
//
// If the provided I/O handle is seekable, the Content-Length header is added
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
export fn put(
client: *client,
target: *uri::uri,
body: io::handle,
) (response | error) = {
const req = new_request_body(client, PUT, target, body);
defer request_finish(&req);
return do(client, &req);
};