HTTP client
Call other services over HTTP.
On this page
valk.http sends requests to other services over HTTP and HTTPS. Each call waits for the whole response but only pauses the current coroutine, so several requests can run side by side with co.
Make a request#
use valk.http
fn main() {
let res = http.get("https://example.com") ! {
println("Request failed: " + E.code)
return
}
println(res.status) // 200
println(res.headers.get("content-type") !? "")
println(res.body.length)
}
http.get, head and delete take a URL; post, put and patch also take a body; http.request(method, url) sends any method. An error means no response arrived (bad URL, no connection, TLS failure, timeout). A 404 or 500 is a normal response, so check res.status.
Options#
Every function takes an http.Options as its last argument:
use valk.http
fn main() {
let options = http.Options {
query_data: Map[String]{ "q" => "valk lang", "page" => "2" }
timeout_ms: 3000
}
options.set_header("Accept", "application/json")
options.basic_auth("ada", "secret")
let res = http.get("https://api.example.com/search", options) ! return
println(res.body)
}
| Field | Default | Meaning |
|---|---|---|
headers |
Extra request headers; or use set_header |
|
body |
"" |
The request body |
query_data |
Parameters added to the URL, encoded | |
timeout_ms |
10000 | Limit for the whole request, redirects included |
connect_timeout_ms |
5000 | Limit for connecting and the TLS handshake |
read_timeout_ms / write_timeout_ms |
5000 | Limit for each socket read or write |
follow_redirects |
true | Follow 301, 302, 303, 307 and 308 |
max_redirects |
10 | Then fail with too_many_redirects |
max_response_body_size |
32 MiB | Larger bodies fail with response_too_large; 0 for no limit |
decompress |
true | Ask for gzip and decompress |
output |
An io.Writer that receives the body instead of res.body |
|
proxy |
from env | http://user:pass@host:port; "" connects directly |
verify_tls_cert |
true | Check the server's certificate |
ca_cert_path |
A CA file to trust instead of the system's | |
client_certificate_file / client_key_file |
A client certificate for mutual TLS |
Without proxy, requests use HTTPS_PROXY / HTTP_PROXY and NO_PROXY from the environment.
Headers#
Header names ignore case, and a name can have several values:
use valk.http
fn main() {
let headers = http.Headers { "Accept" => "application/json" }
headers.append("X-Tag", "one")
headers.append("X-Tag", "two")
println(headers.get("accept") !? "") // application/json
println(headers.get_all("X-TAG").length) // 2
headers.set("X-Tag", "three") // replaces both
println(headers.get_all("x-tag").length) // 1
}
Pass them as http.Options { headers: headers }. A response's res.headers works the same, e.g. res.headers.get_all("set-cookie").
Send and receive JSON#
use valk.http
use valk.json
class NewItem {
name: String
price: float
}
class Item {
id: int
name: String
}
fn main() {
let options = http.Options {}
options.set_header("Content-Type", "application/json")
let res = http.post("https://api.example.com/items", json.encode(NewItem { name: "Pen", price: 2.5 }), options) ! return
if res.status != 201 {
println("Failed with " + res.status)
return
}
let item = json.decode_to[Item](res.body) ! return
println(item.id)
}
See JSON for decoding options.
Forms and uploads#
A form body is a query string, built with url.build_query. http.Multipart builds a file upload:
use valk.http
fn main() {
let form = http.Multipart.new()
form.add_field("title", "Holiday")
form.add_file("photo", "beach.jpg", "...image bytes...", "image/jpeg")
let options = http.Options {}
form.apply(options) // sets the body and Content-Type
let res = http.request("POST", "https://example.com/upload", options) ! return
println(res.status)
}
Download to a file#
use valk.http
fn main() {
http.download("https://example.com/archive.zip", "archive.zip") ! {
println("Download failed: " + E.code)
return
}
}
download streams the body to the file and throws status for a 4xx or 5xx answer. To send a body to any other writer, set Options.output.
Reuse connections with a Client#
Each http.get opens its own connection. An http.Client keeps connections open per host and keeps the cookies that servers set:
use valk.http
fn main() {
let client = http.Client.new()
client.post("https://example.com/login", "user=ada&password=secret") ! return
let page = client.get("https://example.com/account") ! return // sends the session cookie
println(page.status)
println(client.cookies.length)
client.close()
}
A client has the same get, post, put, patch, delete, head and request methods. max_idle_per_host and idle_timeout_ms tune the pool, and close() drops idle connections. A client belongs to the thread that made it; its coroutines can share it.
Cookies#
res.cookies() returns the http.Cookies a response set. A Client keeps them in client.cookies, a CookieJar:
| Method | Does |
|---|---|
cookies_for(url) |
The cookies a request to url would send |
header_for(url) |
The Cookie header for url |
set(url, cookie) |
Adds a cookie as if url had set it |
clear() |
Removes all |
Redirects#
Redirects are followed by default, up to max_redirects. A 303 (and a 301 or 302 after a POST) continues as a GET without the body. Authorization and Cookie headers you set are not sent along to another host. Set follow_redirects: false to get the redirect response itself and read its location header.
TLS#
HTTPS certificates are checked against the system's CA store. For a private CA, set ca_cert_path; to pin a certificate, set certificate_sha256. min_tls_version (TLS 1.2 by default) and max_tls_version limit the protocol versions. Only turn off verify_tls_cert for local tests.
