145 lines
3.3 KiB
Hare
145 lines
3.3 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.
|
||
|
//
|
||
|
// 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);
|
||
|
};
|