Docs/Networking

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#

Valk
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:

Valk
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:

Valk
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#

Valk
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:

Valk
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#

Valk
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:

Valk
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.