use bufio; use encoding::utf8; use fmt; use io; use net::dial; use net::uri; use net; use os; use strconv; use strings; // 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; }; // TODO: Improve error handling let resp = response { ... }; const scan = bufio::newscanner_static(conn, buf); read_statusline(&resp, &scan)?; read_header(&resp.header, &scan)?; const remain = bufio::scan_buffer(&scan); const rd = alloc(reader { vtable = &reader_vtable, conn = conn, ... }); rd.buffer[..len(remain)] = remain[..]; rd.pending = len(remain); resp.body = rd; return resp; }; fn read_statusline( resp: *response, scan: *bufio::scanner, ) (void | io::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 => abort(); // TODO case io::EOF => abort(); // TODO }; // TODO: Error handling const tok = strings::tokenize(status, " "); const version = strings::next_token(&tok) as str; const status = strings::next_token(&tok) as str; const reason = strings::next_token(&tok) as str; assert(version == "HTTP/1.1"); // TODO const (_, version) = strings::cut(version, "/"); const (major, minor) = strings::cut(version, "."); resp.version = ( strconv::stou(major)!, strconv::stou(minor)!, ); resp.status = strconv::stou(status)!; resp.reason = strings::dup(reason); };