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.
The dependencies
Section titled “The dependencies”cargo new fetch gives you an empty binary project. Two lines under the [dependencies] section it already wrote are all it needs:
[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.
The request
Section titled “The request”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:
#[tokio::main]async fn main() -> Result<(), reqwest::Error> { let url = "https://httpbin.org/json";
let response = reqwest::get(url).await?; let status = response.status(); let body = response.text().await?;
println!("{status}"); println!("{body}"); Ok(())}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.
What you get back
Section titled “What you get back”.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:
#[tokio::main]async fn main() { match reqwest::get("example.com").await { Ok(response) => println!("{}", response.status()), Err(error) => println!("{error:?}"), }}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:
- How to deserialize JSON into a struct — what to do when the text you get back is JSON, as it is above.
- Making a real HTTP request with reqwest — the same request as one step inside a CLI.
- Async fundamentals — the runtime behind
#[tokio::main], and the futures it drives.