use fmt; use io; use net::ip; use net::uri; use strconv; use strings; export type client = struct { default_header: header, }; // Creates a new HTTP [[client]] with the provided User-Agent string, which is // borrowed from the caller. Pass the return value to [[client_finish]] to free // resourfces associated with the HTTP client after use. export fn newclient(ua: str) client = { let client = client { ... }; client_add_header(&client, "User-Agent", ua); return client; }; // Frees resources associated with an HTTP [[client]]. export fn client_finish(client: *client) void = { header_free(&client.default_header); }; // Adds a default header which is included on all HTTP requests using a given // [[client]]. export fn client_add_header(client: *client, name: str, val: str) void = { add_header(&client.default_header, name, val); }; fn new_request(client: *client, method: str, target: *uri::uri) request = { let req = request { method = method, target = alloc(uri::dup(target)), header = alloc(client.default_header...), body = void, }; if (req.target.port == 0) { switch (req.target.scheme) { case "http" => req.target.port = 80; case "https" => req.target.port = 443; }; }; let host = match (req.target.host) { case let host: str => yield host; case let ip: ip::addr4 => yield ip::string(ip); case let ip: ip::addr6 => static let buf: [64 + 2]u8 = [0...]; yield fmt::bsprintf(buf, "[{}]", ip::string(ip)); }; if (req.target.scheme == "http" && req.target.port != 80) { host = fmt::asprintf("{}:{}", host, req.target.port); } else if (target.scheme == "https" && target.port != 443) { host = fmt::asprintf("{}:{}", host, req.target.port); } else { host = strings::dup(host); }; append(req.header, ("Host", host)); return req; }; fn new_request_body( client: *client, method: str, target: *uri::uri, body: io::handle, ) request = { let req = new_request(client, method, target); req.body = body; request_set_content_length(&req, body); return req; }; fn request_set_content_length(req: *request, body: io::handle) void = { const prev = match (io::seek(body, 0, io::whence::CUR)) { case let off: io::off => yield off; case io::error => return; }; const ln = io::seek(body, 0, io::whence::END)!; io::seek(body, prev, io::whence::SET)!; add_header(&req.header, "Content-Length", strconv::ztos(ln: size)); }; fn uri_origin_form(target: *uri::uri) uri::uri = { let target = *target; target.scheme = ""; target.host = ""; target.fragment = ""; target.userinfo = ""; target.port = 0; if (target.path == "") { target.path = "/"; }; return target; }; // Prepares a new HTTP GET request for the given client and fills in the default // headers, such as User-Agent and Host. Provide the return value to [[do]] to // execute the request and free associated resources, or use [[request_finish]] // to free resources without executing the request. // // The URI parameter is borrowed from the caller for the lifetime of the request // object. export fn get(client: *client, target: *uri::uri) request = { return new_request(client, GET, target); }; // Prepares a new HTTP HEAD request for the given client and fills in the // default headers, such as User-Agent and Host. Provide the return value to // [[do]] to execute the request and free associated resources, or use // [[request_finish]] to free resources without executing the request. // // The URI parameter is borrowed from the caller for the lifetime of the request // object. export fn head(client: *client, target: *uri::uri) request = { return new_request(client, HEAD, target); }; // Prepares a new HTTP POST request for the given client and fills in the // default headers, such as User-Agent and Host. Provide the return value to // [[do]] to execute the request and free associated resources, or use // [[request_finish]] to free resources without executing the request. // // If the provided I/O handle is seekable, the Content-Length header is added // automatically. Otherwise, Transfer-Encoding: chunked will be used. // // The URI parameter is borrowed from the caller for the lifetime of the request // object. export fn post(client: *client, target: *uri::uri, body: io::handle) request = { return new_request_body(client, POST, target, body); }; // Prepares a new HTTP PUT request for the given client and fills in the // default headers, such as User-Agent and Host. Provide the return value to // [[do]] to execute the request and free associated resources, or use // [[request_finish]] to free resources without executing the request. // // If the provided I/O handle is seekable, the Content-Length header is added // automatically. Otherwise, Transfer-Encoding: chunked will be used. // // The URI parameter is borrowed from the caller for the lifetime of the request // object. export fn put(client: *client, target: *uri::uri, body: io::handle) request = { return new_request_body(client, POST, target, body); };