diff --git a/cmd/http/main.ha b/cmd/http/main.ha new file mode 100644 index 0000000..8c05575 --- /dev/null +++ b/cmd/http/main.ha @@ -0,0 +1,21 @@ +use net::http; +use net::ip; +use net::uri; +use os; + +export fn main() void = { + const req = &http::request { + method = http::GET, + target = &uri::uri { + scheme = "http", + host = ip::LOCAL_V6, + path = "/", + ... + }, + body = void, + ... + }; + http::add_header(&req.header, "User-Agent", "Hare test client"); + http::add_header(&req.header, "Content-Length", "100"); + http::write_header(os::stdout, &req.header)!; +}; diff --git a/net/http/README b/net/http/README new file mode 100644 index 0000000..1045060 --- /dev/null +++ b/net/http/README @@ -0,0 +1,8 @@ +TODO: Flesh me out + +Caveats: + +- No attempt is made to validate that the input for client requests or responses + are valid according to the HTTP grammar; such cases will fail when rejected by + the other party. +- Details indicated by RFC 7230 et al as "obsolete" are not implemented diff --git a/net/http/header.ha b/net/http/header.ha new file mode 100644 index 0000000..12f8200 --- /dev/null +++ b/net/http/header.ha @@ -0,0 +1,42 @@ +use io; +use fmt; + +// List of HTTP headers. +export type header = [](str, str); + +// Adds a given HTTP header, which may be added more than once. The provided +// name and value are borrowed from the caller. The provided header name should +// be canonicalized by the caller. +export fn add_header(head: *header, name: str, val: str) void = { + append(head, (name, val)); +}; + +// Sets the value of a given HTTP header, removing any previous values. The +// provided name and value are borrowed from the caller. The provided header +// name should be canonicalized by the caller. +export fn set_header(head: *header, name: str, val: str) void = { + del_header(head, name); + add_header(head, name, val); +}; + +// Removes an HTTP header from a list of [[header]]. If multiple headers match +// the given name, all matching headers are removed. +export fn del_header(head: *header, name: str) void = { + for (let i = 0z; i < len(head); i += 1) { + if (head[i].0 == name) { + delete(head[i]); + i -= 1; + }; + }; +}; + +// Writes a list of HTTP headers to the provided I/O handle in the HTTP wire +// format. +export fn write_header(sink: io::handle, head: *header) (size | io::error) = { + let z = 0z; + for (let i = 0z; i < len(head); i += 1) { + const (name, val) = head[i]; + z += fmt::fprintf(sink, "{}: {}\r\n", name, val)?; + }; + return z; +}; diff --git a/net/http/request.ha b/net/http/request.ha index e69de29..d710a81 100644 --- a/net/http/request.ha +++ b/net/http/request.ha @@ -0,0 +1,20 @@ +use io; +use net::uri; + +// Stores state related to an HTTP request. +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, + + // I/O reader for the request body. + body: (io::handle | void), +};