///|
pub(all) enum Protocol {
Http
Https
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn Protocol::default_port(p : Protocol) -> Int {
match p {
Http => 80
Https => 443
}
}
///|
pub suberror URIParseError {
InvalidFormat
UnsupportedProtocol(String)
} derive(Debug, ToJson)
///|
#warnings("-unused_constructor")
pub suberror RequestError {
UnsupportedHttpVersion(HttpVersion)
} derive(Debug, ToJson)
///|
#cfg(target="native")
fn ensure_supported_http_version(version : HttpVersion) -> Unit {
ignore(version)
}
///|
#cfg(target="js")
fn ensure_supported_http_version(_version : HttpVersion) -> Unit {
()
}
///|
fn resolve_url(uri : String) -> (Protocol, Int, String, String) raise {
guard uri.find("://") is Some(protocol_len) else { raise InvalidFormat }
let protocol = match uri[:protocol_len] {
"http" => Http
"https" => Https
protocol => raise UnsupportedProtocol(protocol.to_owned())
}
let uri = uri[protocol_len + 3:]
let (host, path) = if uri.find("/") is Some(i) {
(uri[:i].to_owned(), uri[i:].to_owned())
} else {
(uri.to_owned(), "/")
}
let (host, port) = if host.find(":") is Some(port_start) {
let port_str = host[port_start + 1:]
let port = @string.parse_int(port_str) catch { _ => raise InvalidFormat }
guard port is (1..<65536) else { raise InvalidFormat }
(host[:port_start].to_owned(), port)
} else {
(host, protocol.default_port())
}
let path = if path == "" { "/" } else { path }
(protocol, port, host, path)
}
///|
#cfg(target="native")
async fn perform_request(
uri : String,
meth : RequestMethod,
headers : Map[String, String],
body : &@io.Data,
proxy? : Client,
version? : HttpVersion = Http1,
verify? : Bool = true,
) -> (Response, &@io.Data) {
ensure_supported_http_version(version)
match version {
Http2 => {
let response_body = h2_perform_request(
uri,
meth,
headers,
body,
proxy?,
verify~,
)
return (response_body.response, response_body.body)
}
Http3 => {
let response_body = h3_perform_request(uri, meth, headers, body, verify~)
return (response_body.response, response_body.body)
}
Http1 => ()
}
let (protocol, port, host, path) = resolve_url(uri)
let client = Client::connect(
host,
headers~,
protocol~,
port~,
proxy?,
verify~,
)
defer client.close()
let response = client..request(meth, path)..write(body).end_request()
(response, client.read_all())
}
///|
#cfg(target="js")
async fn perform_request(
uri : String,
meth : RequestMethod,
headers : Map[String, String],
body : &@io.Data,
proxy? : Client,
version? : HttpVersion = Http1,
verify? : Bool = true,
) -> (Response, &@io.Data) {
ignore(verify)
ensure_supported_http_version(version)
let (protocol, port, host, path) = resolve_url(uri)
let client = Client::connect(host, headers~, protocol~, port~, proxy?)
defer client.close()
let response = client..request(meth, path)..write(body).end_request()
(response, client.read_all())
}
///|
async fn perform_request_body_with_options(
uri : String,
meth : RequestMethod,
headers : Map[String, String],
body : &@io.Data,
proxy? : Client,
version? : HttpVersion = Http1,
verify? : Bool = true,
) -> ResponseBody {
let (response, body) = perform_request(
uri,
meth,
headers,
body,
proxy?,
version~,
verify~,
)
{ response, body }
}
///|
async fn perform_request_body(
uri : String,
meth : RequestMethod,
headers : Map[String, String],
body : &@io.Data,
proxy? : Client,
) -> ResponseBody {
let (response, body) = perform_request(uri, meth, headers, body, proxy?)
{ response, body }
}
///|
/// Perform a HTTP `GET` request to `uri`.
/// Supported protocols are `http://` and `https://`.
/// The HTTP response message and the whole response body will be returned.
///
/// `proxy`, if present, specifies the proxy to use for this request.
/// See `@http.Client::new` for more details.
/// `proxy` is not supported on JavaScript backend.
///
/// See `Client::request` for more details.
pub async fn get(
uri : String,
headers? : Map[String, String] = {},
body? : &@io.Data = b"",
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Get, headers, body, proxy?)
}
///|
/// Similar to `get`, but performs a `PUT` request instead.
pub async fn put(
uri : String,
content : &@io.Data,
headers? : Map[String, String] = {},
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Put, headers, content, proxy?)
}
///|
/// Similar to `get`, but performs a `POST` request instead.
pub async fn post(
uri : String,
content : &@io.Data,
headers? : Map[String, String] = {},
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Post, headers, content, proxy?)
}
///|
/// Similar to `get`, but performs a `DELETE` request instead.
pub async fn delete(
uri : String,
headers? : Map[String, String] = {},
body? : &@io.Data = b"",
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Delete, headers, body, proxy?)
}
///|
/// Similar to `get`, but performs a `PATCH` request instead.
pub async fn patch(
uri : String,
content : &@io.Data,
headers? : Map[String, String] = {},
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Patch, headers, content, proxy?)
}
///|
/// Similar to `get`, but performs a `HEAD` request instead.
pub async fn head(
uri : String,
headers? : Map[String, String] = {},
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Head, headers, b"", proxy?)
}
///|
/// Similar to `get`, but performs an `OPTIONS` request instead.
pub async fn options(
uri : String,
headers? : Map[String, String] = {},
body? : &@io.Data = b"",
proxy? : Client,
) -> ResponseBody {
perform_request_body(uri, Options, headers, body, proxy?)
}
///|
/// Similar to `@http.get`, but allow reading response body streamingly.
/// A pair `(response, client)` will be returned,
/// where `response` is the response header from the server,
/// and `client` is the HTTP client that performs the request.
/// `client` can be used to read the content of response body via `@io.Reader`,
/// see `@http.Client` for more details.
///
/// Note that the returned client must be manually closed via `.close()`
/// to close the underlying connection used for the request.
pub async fn get_stream(
uri : String,
headers? : Map[String, String] = {},
body? : &@io.Data = b"",
proxy? : Client,
) -> (Response, Client) {
let (protocol, port, host, path) = resolve_url(uri)
let client = Client::connect(host, headers~, protocol~, port~, proxy?)
try {
let response = client..request(Get, path)..write(body).end_request()
(response, client)
} catch {
err => {
client.close()
raise err
}
}
}
///|
/// Similar to `@http.put`, but allow writing request body streamingly.
/// The return value `client` is the HTTP client that performs the request,
/// it can be used to write the content of request body via `@io.Writer`.
/// Notice that writing to `@http.Client` is buffered,
/// so if you need to send data to the server immediately, `.flush()` must be called.
/// After writing all the content, `.end_request()` must be called
/// to complete the request and obtain response from the server.
/// After that, the response body from the server can be obtained
/// by using `client` as a `@io.Reader`. See `@http.Client` for more details.
///
/// Note that the returned `client` must be manually closed via `.close()`
/// to close the underlying connection used for the request.
pub async fn put_stream(
uri : String,
headers? : Map[String, String] = {},
proxy? : Client,
) -> Client {
let (protocol, port, host, path) = resolve_url(uri)
let client = Client::connect(host, protocol~, port~, proxy?)
try client.request(Put, path, extra_headers=headers) catch {
err => {
client.close()
raise err
}
} noraise {
_ => client
}
}
///|
/// Similar to `@http.post`, but allow writing request body streamingly.
/// The return value `client` is the HTTP client that performs the request,
/// it can be used to write the content of request body via `@io.Writer`.
/// Notice that writing to `@http.Client` is buffered,
/// so if you need to send data to the server immediately, `.flush()` must be called.
/// After writing all the content, `.end_request()` must be called
/// to complete the request and obtain response from the server.
/// After that, the response body from the server can be obtained
/// by using `client` as a `@io.Reader`. See `@http.Client` for more details.
///
/// Note that the returned `client` must be manually closed via `.close()`
/// to close the underlying connection used for the request.
pub async fn post_stream(
uri : String,
headers? : Map[String, String] = {},
proxy? : Client,
) -> Client {
let (protocol, port, host, path) = resolve_url(uri)
let client = Client::connect(host, protocol~, port~, proxy?)
try client.request(Post, path, extra_headers=headers) catch {
err => {
client.close()
raise err
}
} noraise {
_ => client
}
}