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.
Troubleshooting
missing WiFi name/password: ensure that you've configuredcfg.tomlaccording 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.