HTTP Client
The goal of this exercise is to write a small HTTP client that connects to a website.
Setup
✅ Go to intro/http-client
directory.
✅ Open the prepared project skeleton in intro/http-client
.
✅ Add your network credentials to the cfg.toml
as in the hardware test.
✅ Open the docs for this project with the following command:
cargo doc --open
intro/http-client/examples/http_client.rs
contains the solution. You can run it with the following command:
cargo run --example http_client
Making a Connection
By default, only unencrypted HTTP is available, which rather limits our options of hosts to connect to. We're going to use http://neverssl.com/
.
In ESP-IDF, HTTP client connections are managed by http::client::EspHttpClient
in the esp-idf-svc
crate. It implements the http::client::Client
trait from embedded-svc
, which defines functions for HTTP request methods like GET
or POST
. This is a good time to have a look at the documentation you opened with cargo doc --open
for esp_idf_svc::http::client::EspHttpConnection
and embedded_svc::http::client::Client
. See instantiation methods at your disposal.
✅ Create a new EspHttpConnection
with default configuration. Look for a suitable constructor in the documentation.
✅ Get a client from the connection you just made.
Calling HTTP functions (e.g. get(url)
) on this client returns an embedded_svc::http::client::Request
, which must be submitted to reflect the client's option to send some data alongside its request.
The get
function uses as_ref(). This means that instead of being restricted to one specific type like just String
or just &str
, the function can accept anything that implements the AsRef<str>
trait. That is any type where a call to .as_ref()
will produce a &str
. This works for String
and &str
, but also the Cow<str>
enum type which contains either of the previous two.
#![allow(unused)] fn main() { let request = client.request(Method::Get, url.as_ref(), &headers)?; }
A successful response has a status code in the 2xx range. Followed by the raw html of the website.
✅ Verify the connection was successful.
✅ Return an Error if the status isn't in the 2xx range.
#![allow(unused)] fn main() { match status { 200..=299 => { } _ => bail!("Unexpected response code: {}", status), } }
The status error can be returned with the Anyhow, crate which contains various functionality to simplify application-level error handling. It supplies a universal anyhow::Result<T>
, wrapping the success (Ok
) case in T and removing the need to specify the Err type, as long as every error you return implements std::error::Error
.
✅ Read the received data chunk by chunk into an u8
buffer using Read::read(&mut reader,&mut buf)
. Read::read
returns the number of bytes read - you're done when this value is 0
.
✅ Report the total number of bytes read.
✅ Log the received data to the console.
💡 The response in the buffer is in bytes, so you might need a method to convert from bytes to &str
.
Extra Tasks
✅ Handle 3xx, 4xx and 5xx status codes each in a separate match arm
✅ Write a custom Error
enum to represent these errors. Implement the std::error::Error
trait for your error.
Simulation
This project is available for simulation through two methods:
- Wokwi projects:
- Wokwi files are also present in the project folder to simulate it with Wokwi VS Code extension:
- Press F1, select
Wokwi: Select Config File
and chooseintro/http-client/wokwi.toml
- Edit the
wokwi.toml
file to select between exercise and solution simulation
- Edit the
- Build you project
- Press F1 again and select
Wokwi: Start Simulator
- Press F1, select
Troubleshooting
missing WiFi name/password
: ensure that you've configuredcfg.toml
according tocfg.toml.example
- a common problem is that the package name and config section name don't match.
# Cargo.toml
#...
[package]
name = "http-client"
#...
# cfg.toml
[http-client]
wifi_ssid = "..."
wifi_psk = "..."
Guru Meditation Error: Core 0 panic'ed (Load access fault). Exception was unhandled.
This may be caused by an.unwrap()
in your code. Try replacing those with question marks.