1
Fork 0
hare-http/net/http/client.ha
2023-02-09 23:23:05 +01:00

144 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,
};
// Creates a new HTTP [[client]] with the provided User-Agent string, which is
// borrowed from the caller. Pass the return value to [[client_finish]] to free
// resourfces associated with the HTTP client after use.
export fn newclient(ua: str) client = {
let client = client { ... };
client_add_header(&client, "User-Agent", ua);
return client;
};
// Frees resources associated with an HTTP [[client]].
export fn client_finish(client: *client) void = {
for (let i = 0z; i < len(client.default_header); i += 1) {
free(client.default_header[i].1);
};
free(client.default_header);
};
// Adds a default header which is included on all HTTP requests using a given
// [[client]].
export fn client_add_header(client: *client, name: str, val: str) void = {
add_header(&client.default_header, name, val);
};
fn new_request(client: *client, method: str, target: *uri::uri) request = {
let req = request {
method = method,
target = alloc(uri::dup(target)),
header = alloc(client.default_header...),
body = void,
};
if (req.target.port == 0) {
switch (req.target.scheme) {
case "http" =>
req.target.port = 80;
case "https" =>
req.target.port = 443;
};
};
let host = match (req.target.host) {
case let host: str =>
yield host;
case let ip: ip::addr =>
yield 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);
};
append(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;
request_set_content_length(&req, body);
return req;
};
fn request_set_content_length(req: *request, body: io::handle) void = {
const prev = match (io::seek(body, 0, io::whence::CUR)) {
case let off: io::off =>
yield off;
case io::error =>
return;
};
const ln = io::seek(body, 0, io::whence::END)!;
io::seek(body, prev, io::whence::SET)!;
add_header(&req.header, "Content-Length", strconv::ztos(ln: size));
};
// Prepares a new HTTP GET request for the given client and fills in the default
// headers, such as User-Agent and Host. Provide the return value to [[do]] to
// execute the request and free associated resources, or use [[request_finish]]
// to free resources without executing the request.
//
// The URI parameter is borrowed from the caller for the lifetime of the request
// object.
export fn get(client: *client, target: *uri::uri) request = {
return new_request(client, GET, target);
};
// Prepares a new HTTP HEAD request for the given client and fills in the
// default headers, such as User-Agent and Host. Provide the return value to
// [[do]] to execute the request and free associated resources, or use
// [[request_finish]] to free resources without executing the request.
//
// The URI parameter is borrowed from the caller for the lifetime of the request
// object.
export fn head(client: *client, target: *uri::uri) request = {
return new_request(client, HEAD, target);
};
// Prepares a new HTTP POST request for the given client and fills in the
// default headers, such as User-Agent and Host. Provide the return value to
// [[do]] to execute the request and free associated resources, or use
// [[request_finish]] to free resources without executing the request.
//
// If the provided I/O handle is seekable, the Content-Length header is added
// automatically.
//
// The URI parameter is borrowed from the caller for the lifetime of the request
// object.
export fn post(client: *client, target: *uri::uri, body: io::handle) request = {
return new_request_body(client, POST, target, body);
};
// Prepares a new HTTP PUT request for the given client and fills in the
// default headers, such as User-Agent and Host. Provide the return value to
// [[do]] to execute the request and free associated resources, or use
// [[request_finish]] to free resources without executing the request.
//
// If the provided I/O handle is seekable, the Content-Length header is added
// automatically.
//
// The URI parameter is borrowed from the caller for the lifetime of the request
// object.
export fn put(client: *client, target: *uri::uri, body: io::handle) request = {
return new_request_body(client, POST, target, body);
};