2023-10-08 09:14:26 +00:00
|
|
|
use errors;
|
|
|
|
use fmt;
|
2023-02-09 19:54:21 +00:00
|
|
|
use io;
|
2023-10-08 09:14:26 +00:00
|
|
|
use net::ip;
|
2023-02-09 19:54:21 +00:00
|
|
|
use net::uri;
|
2023-10-08 09:14:26 +00:00
|
|
|
use strconv;
|
|
|
|
use strings;
|
2023-02-09 19:54:21 +00:00
|
|
|
|
|
|
|
// Stores state related to an HTTP request.
|
2023-02-12 12:05:11 +00:00
|
|
|
//
|
|
|
|
// For a request to be processable by an HTTP [[client]], i.e. via [[do]], the
|
|
|
|
// method and target must be filled in appropriately. The target must include at
|
|
|
|
// least a host, port, and scheme. The default values for other fields are
|
|
|
|
// suitable if appropriate for the request you wish to perform.
|
2023-02-09 19:54:21 +00:00
|
|
|
export type request = struct {
|
|
|
|
// HTTP request method, e.g. GET
|
|
|
|
method: str,
|
|
|
|
// Request target URI.
|
|
|
|
//
|
|
|
|
// Note that the normal constraints for [[uri::parse]] are not upheld in
|
|
|
|
// the case of a request using the origin-form (e.g. /index.html), i.e.
|
|
|
|
// the scheme field may be empty.
|
|
|
|
target: *uri::uri,
|
|
|
|
|
|
|
|
// List of HTTP request headers.
|
|
|
|
header: header,
|
|
|
|
|
2023-02-12 11:55:16 +00:00
|
|
|
// Transport configuration, or null to use the [[client]] default.
|
|
|
|
transport: nullable *transport,
|
|
|
|
|
2023-02-12 12:05:11 +00:00
|
|
|
// I/O reader for the request body, or void if there is no body.
|
2023-02-09 19:54:21 +00:00
|
|
|
body: (io::handle | void),
|
|
|
|
};
|
2023-02-09 22:23:05 +00:00
|
|
|
|
|
|
|
// Frees state associated with an HTTP [[request]].
|
|
|
|
export fn request_finish(req: *request) void = {
|
2023-02-10 09:13:30 +00:00
|
|
|
header_free(&req.header);
|
2023-02-09 22:23:05 +00:00
|
|
|
uri::finish(req.target);
|
|
|
|
free(req.target);
|
|
|
|
};
|
2023-10-08 09:14:26 +00:00
|
|
|
|
|
|
|
// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults.
|
|
|
|
export fn new_request(
|
|
|
|
client: *client,
|
|
|
|
method: str,
|
|
|
|
target: *uri::uri,
|
|
|
|
) (request | errors::unsupported) = {
|
|
|
|
let req = request {
|
|
|
|
method = method,
|
|
|
|
target = alloc(uri::dup(target)),
|
|
|
|
header = header_dup(&client.default_header),
|
|
|
|
transport = null,
|
|
|
|
body = void,
|
|
|
|
};
|
|
|
|
switch (req.target.scheme) {
|
|
|
|
case "http" =>
|
|
|
|
if (req.target.port == 0) {
|
|
|
|
req.target.port = 80;
|
|
|
|
};
|
|
|
|
case "https" =>
|
|
|
|
if (req.target.port == 0) {
|
|
|
|
req.target.port = 443;
|
|
|
|
};
|
|
|
|
case =>
|
|
|
|
return errors::unsupported;
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults and
|
|
|
|
// the provided request body.
|
|
|
|
//
|
|
|
|
// If the provided I/O handle is seekable, the Content-Length header is added
|
|
|
|
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
|
|
|
export fn new_request_body(
|
|
|
|
client: *client,
|
|
|
|
method: str,
|
|
|
|
target: *uri::uri,
|
|
|
|
body: io::handle,
|
|
|
|
) (request | errors::unsupported) = {
|
|
|
|
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;
|
|
|
|
};
|