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")?; write_header(&file, &req.header)?; fmt::fprintf(&file, "\r\n")?; bufio::flush(&file)?; match (req.body) { case let body: io::handle => 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); };