An HTTP client is the code that allows our device to make requests to web servers and APIs.
We already have Wi-Fi and we already have the exact time. Now it’s time to browse.
Obviously, our microcontroller isn’t going to open YouTube, but it does need to behave like a web browser: it needs to send Requests to servers and process their Responses.
In the IoT world, the standard for this is to use the HTTP/HTTPS protocol against REST APIs.
- GET: “Hey server, give me information” (e.g., What’s the weather like?).
- POST: “Hey server, take this data” (e.g., Here is the temperature from my living room).
In this post, we will learn how to use HttpClient, whose API will feel familiar if you’ve already worked with desktop .NET.
For this tutorial, you need to install the NuGet package: nanoFramework.System.Net.Http.Client
The Request-Response Cycle
Before writing code, let’s visualize what happens.
- Client (ESP32): Prepares a packet with a URL, headers, and optionally a body.
- Network: Travels over the Internet.
- Server: Processes the request and returns a status code (200 OK, 404 Not Found, etc.) and content.
Preparing HttpClient
The main class is HttpClient. It is advisable to reuse an instance for as long as it makes sense in the device’s lifecycle and release it with Dispose when it is no longer needed.
The essential part is configuring SSL options if we are going to use HTTPS (which is 99% of the time nowadays).
using System;
using System.Diagnostics;
using System.Net.Http; // <--- Key namespace
using System.Threading;
// ... inside your method
// Basic option (validates standard certificates present in the firmware)
HttpClient client = new HttpClient();
// Advanced option (if you need specific certificates, they are configured in HttpMessageHandler)GET Request: Reading Information
Let’s make a simple request to a public test API. We’ll use httpbin.org or jsonplaceholder.typicode.com, which are free services for testing HTTP clients.
The flow is: GetAsync -> Wait for response -> Read content.
public static void PerformGETRequest()
{
try
{
// 1. Create the client
using (HttpClient client = new HttpClient())
{
// 2. Define the URL
string url = "https://jsonplaceholder.typicode.com/todos/1";
Debug.WriteLine($"Performing GET to: {url}");
// 3. Execute the request (synchronous or asynchronous)
// In nanoFramework, GetAsync returns an HttpResponseMessage
HttpResponseMessage response = client.Get(url);
// 4. Check the status code (200 OK)
response.EnsureSuccessStatusCode();
// 5. Read the body content
string responseBody = response.Content.ReadAsString();
Debug.WriteLine("Response received:");
Debug.WriteLine(responseBody);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in GET: {ex.Message}");
}
}If everything goes well, you’ll see a JSON like this in the console:
{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
Notice the using (...) { } block. It is crucial. At the end of the block, Dispose() is called, which closes the TCP connection and frees up the valuable RAM of the ESP32.
POST Request: Sending Data
Now let’s simulate being a sensor sending data to the cloud. For this, we use the POST verb.
The main difference is that now we must send a Body and specify what type of content it is (usually JSON).
Since we haven’t seen the JSON library yet (next article), we’ll build the JSON manually as a String.
public static void PerformPOSTRequest()
{
try
{
using (HttpClient client = new HttpClient())
{
string url = "https://httpbin.org/post";
// 1. Prepare the data (manual JSON)
string jsonManual = "{ \"sensor\": \"temp_01\", \"valor\": 24.5 }";
// 2. Package the content
// StringContent(content, encoding, mime-type)
StringContent content = new StringContent(jsonManual, System.Text.Encoding.UTF8, "application/json");
Debug.WriteLine("Sending data...");
// 3. Execute POST
HttpResponseMessage response = client.Post(url, content);
// 4. Check response
if (response.IsSuccessStatusCode)
{
Debug.WriteLine("Data sent successfully!");
string serverResponse = response.Content.ReadAsString();
Debug.WriteLine(serverResponse);
}
else
{
Debug.WriteLine($"Server error: {response.StatusCode}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception in POST: {ex.Message}");
}
}HTTPS and CA Certificates
An important technical note. When you make an https://... request, nanoFramework checks the server’s certificate.
For this to work, the ESP32 must have stored the Root CA of the authority that signed the server’s certificate (e.g., DigiCert, Let’s Encrypt).
The device must have the appropriate root CA, either in the certificate store or provided to the connection. Don’t assume that any service will work with the installed image: check the specific chain and update its certificates when needed.
Remember NTP: If the previous code fails with a security exception, review the previous lesson. Without synchronized time, HTTPS will never work.
Authentication Headers
Many APIs require an API Key or a Token in the headers. Adding them is very easy:
client.DefaultRequestHeaders.Add("Authorization", "Bearer MY_SECRET_TOKEN");
client.DefaultRequestHeaders.Add("X-Api-Key", "123456789");