149 lines
3.9 KiB
Hare
149 lines
3.9 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 = {
|
|
header_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 = {
|
|
header_add(&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 = header_dup(&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::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;
|
|
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)!;
|
|
header_add(&req.header, "Content-Length", strconv::ztos(ln: size));
|
|
};
|
|
|
|
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);
|
|
};
|