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. // // If the provided [[response]] has a non-null body, the user must pass it to // [[io::close]] before calling [[response_finish]]. 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::BUFSZ]u8 = [0...]; let file = bufio::init(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)?; const trans = match (req.transport) { case let t: *transport => yield t; case => yield &client.default_transport; }; // TODO: Implement None assert(trans.request_transport == transport_mode::AUTO); assert(trans.response_transport == transport_mode::AUTO); assert(trans.request_content == content_mode::AUTO); assert(trans.response_content == content_mode::AUTO); match (req.body) { case let body: io::handle => io::copy(conn, body)?; case void => yield; }; let resp = response { ... }; const scan = bufio::newscanner(conn, 512); read_statusline(&resp, &scan)?; read_header(&resp.header, &scan)?; const response_complete = req.method == "HEAD" || resp.status == STATUS_NO_CONTENT || resp.status == STATUS_NOT_MODIFIED || (resp.status >= 100 && resp.status < 200) || (req.method == "CONNECT" && resp.status >= 200 && resp.status < 300); if (!response_complete) { resp.body = new_reader(conn, &resp, &scan)?; } else if (req.method != "CONNECT") { io::close(conn)!; }; 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 done => return protoerr; }; const status = match (strings::next_token(&tok)) { case let status: str => yield status; case done => return protoerr; }; const reason = match (strings::next_token(&tok)) { case let reason: str => yield reason; case done => return protoerr; }; const (_, version) = strings::cut(version, "/"); const (major, minor) = strings::cut(version, "."); const major = match (strconv::stou(major)) { case let u: uint => yield u; case => return protoerr; }; const minor = match (strconv::stou(minor)) { case let u: uint => yield u; case => return protoerr; }; resp.version = (major, minor); if (resp.version.0 > 1) { return errors::unsupported; }; resp.status = match (strconv::stou(status)) { case let u: uint => yield u; case => return protoerr; }; resp.reason = strings::dup(reason); };