1
Fork 0
hare-http/net/http/do.ha
Drew DeVault 946c985a02 Rig out transport handling, finish identity reader
New patch will implement more transports, such as chunked and gzip.
2023-02-11 11:31:07 +01:00

130 lines
2.9 KiB
Hare

use bufio;
use encoding::utf8;
use errors;
use fmt;
use io;
use net::dial;
use net::uri;
use net;
use os;
use strconv;
use strings;
use types;
// Performs an HTTP [[request]] with the given [[client]]. The request is
// performed synchronously; this function blocks until the server has returned
// the response status and all HTTP headers associated with the response.
export fn do(client: *client, req: *request) (response | error) = {
assert(req.target.scheme == "http"); // TODO: https
const conn = dial::dial_uri("tcp", req.target)?;
let buf: [os::BUFSIZ]u8 = [0...];
let file = bufio::buffered(conn, [], buf);
bufio::setflush(&file, []);
fmt::fprintf(&file, "{} ", req.method)?;
// TODO: Support other request-targets than origin-form
const target = uri_origin_form(req.target);
uri::fmt(&file, &target)?;
fmt::fprintf(&file, " HTTP/1.1\r\n")?;
// TODO: Handle Content-Length and Transfer-Encoding chunked/gzip
// properly
write_header(&file, &req.header)?;
fmt::fprintf(&file, "\r\n")?;
bufio::flush(&file)?;
match (req.body) {
case let body: io::handle =>
// Copy to conn directly so we can use sendfile(2) if
// appropriate
io::copy(conn, body)?;
case void =>
yield;
};
let resp = response { ... };
const scan = bufio::newscanner_static(conn, buf);
read_statusline(&resp, &scan)?;
read_header(&resp.header, &scan)?;
const cl = header_get(&resp.header, "Content-Length");
const te = header_get(&resp.header, "Transfer-Encoding");
if (len(cl) > 1 || len(te) > 1) {
return protoerr;
};
if (len(te) == 1) {
abort(); // TODO: Assign transport encoding appropriately
} else {
let length = types::SIZE_MAX;
if (len(cl) == 1) {
length = match (strconv::stoz(cl[0])) {
case let z: size =>
yield z;
case =>
return protoerr;
};
};
const remain = bufio::scan_buffer(&scan);
resp.body = new_identity_reader(conn, remain, length);
};
return resp;
};
fn read_statusline(
resp: *response,
scan: *bufio::scanner,
) (void | error) = {
const status = match (bufio::scan_string(scan, "\r\n")) {
case let line: const str =>
yield line;
case let err: io::error =>
return err;
case utf8::invalid =>
return protoerr;
case io::EOF =>
return protoerr;
};
const tok = strings::tokenize(status, " ");
const version = match (strings::next_token(&tok)) {
case let ver: str =>
yield ver;
case void =>
return protoerr;
};
const status = match (strings::next_token(&tok)) {
case let status: str =>
yield status;
case void =>
return protoerr;
};
const reason = match (strings::next_token(&tok)) {
case let reason: str =>
yield reason;
case void =>
return protoerr;
};
const (_, version) = strings::cut(version, "/");
const (major, minor) = strings::cut(version, ".");
resp.version = (
strconv::stou(major)!,
strconv::stou(minor)!,
);
if (resp.version.0 > 1) {
return errors::unsupported;
};
resp.status = strconv::stou(status)!;
resp.reason = strings::dup(reason);
};