Skip to content
Select theme

How to make a GET request

Short answer: Add reqwest and tokio to Cargo.toml, mark main with #[tokio::main], and wait on reqwest::get(url).await?.text().await?. The first future gets the response; the second reads the whole body into a String.

cargo new fetch gives you an empty binary project. Two lines under the [dependencies] section it already wrote are all it needs:

Cargo.toml
[dependencies]
reqwest = "0.13.5"
tokio = { version = "1.53.3", features = ["macros", "rt-multi-thread"] }

reqwest is async, so every call hands back a future that does nothing until something polls it. That something is a runtime, and tokio is the one reqwest is built around. macros provides the #[tokio::main] attribute; rt-multi-thread provides the multi-threaded scheduler that attribute starts by default. reqwest’s own default features include TLS, so https:// URLs work with no extra configuration.

This is the whole program, src/main.rs. It fetches https://httpbin.org/json, a page that always answers with the same fixed JSON, so the output below is stable:

Run it with cargo run (cargo’s own Compiling and Finished lines are not shown here):

200 OK
{
"slideshow": {
"author": "Yours Truly",
"date": "date of publication",
"slides": [
{
"title": "Wake up to WonderWidgets!",
"type": "all"
},
{
"items": [
"Why <em>WonderWidgets</em> are great",
"Who <em>buys</em> WonderWidgets"
],
"title": "Overview",
"type": "all"
}
],
"title": "Sample Slide Show"
}
}

reqwest::get(url) sends the request and returns a future; .await suspends main until the response headers are in, and the first ? turns a connection failure into an early return. response.text().await consumes the response and reads the entire body, with the second ? covering a body that fails mid-read. #[tokio::main] is what makes an async fn main legal at all: the macro rewrites it into an ordinary fn main that builds the runtime, then blocks on the async body until it finishes.

.text() gives you the body as a String. Decoding follows the charset in the response’s Content-Type header and falls back to UTF-8 when there is none; a malformed byte becomes U+FFFD rather than failing the call. The body arrives whole and in memory, which is fine for a page and wasteful for something the size of a film.

A URL that is not a URL fails before anything is sent. No socket is opened and no round trip is spent. Here is the error, printed rather than propagated:

reqwest::Error { kind: Builder, source: RelativeUrlWithoutBase }

That is the Debug form of reqwest::Error. Leave the same error unhandled (return it from main instead of matching it) and the runtime prints it under an Error: line and exits with a non-zero status. A host that will not resolve, a refused connection and a failed body read all arrive as this one error type, and ? treats them the same way.

The next article in this chapter puts a body on the request. Until it is up, these cover the ground around this one: