# Connectivity with Zephyr - Part 2: HTTP client on ESP32-S3

**URL:** https://hub.mender.io/t/connectivity-with-zephyr-part-2-http-client-on-esp32-s3/8131
**Category:** Zephyr
**Created:** [December 9, 2025, 2:32pm UTC](https://hub.mender.io/t/connectivity-with-zephyr-part-2-http-client-on-esp32-s3/8131 "2025-12-09T14:32:01Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![TheYoctoJester](https://yyz2.discourse-cdn.com/flex036/user_avatar/hub.mender.io/theyoctojester/32/1444_2.png) [@TheYoctoJester](https://hub.mender.io/u/TheYoctoJester)
#### Post date: [December 9, 2025, 2:32pm UTC](https://hub.mender.io/t/connectivity-with-zephyr-part-2-http-client-on-esp32-s3/8131/1 "2025-12-09T14:32:01Z")

</div>

With WiFi connectivity established, we can now make HTTP requests to fetch data from a web API. This part demonstrates DNS resolution and HTTP GET requests using Zephyr’s socket and HTTP client APIs.

| | |
| --- | --- |
| **Target** | ESP32-S3-DevKitC |
| **Zephyr version** | 4.3.0 |
| **Level** | Beginner |

**Tutorial Series:**

1. [WiFi Connectivity](https://hub.mender.io/t/connectivity-with-zephyr-part-1-wifi-on-esp32-s3/8130)
2. **HTTP Client** (this part)
3. [HTTPS with TLS](https://hub.mender.io/t/connectivity-with-zephyr-part-3-https-with-tls-on-esp32-s3/8132)

## Prerequisites

**This tutorial requires:**

- Completed [Part 1: WiFi Connectivity](https://hub.mender.io/t/connectivity-with-zephyr-part-1-wifi-on-esp32-s3/8130) with a working WiFi connection
- Basic understanding of TCP/IP sockets and the HTTP protocol

**Familiarity with these concepts is helpful:**

- [BSD Sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) - the standard socket API used here
- [HTTP/1.1 Protocol](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview) - the application protocol for web requests
- [DNS Resolution](https://www.cloudflare.com/learning/dns/what-is-dns/) - how hostnames are translated to IP addresses

## Configuration

Add the following to `prj.conf` to enable sockets, HTTP client, and DNS resolution:

```ini
# Sockets
CONFIG_NET_SOCKETS=y
CONFIG_POSIX_API=y

# HTTP Client
CONFIG_HTTP_CLIENT=y

# DNS Resolver
CONFIG_DNS_RESOLVER=y
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="8.8.8.8"

```

The `CONFIG_POSIX_API` option provides familiar BSD socket functions like `getaddrinfo()`, `socket()`, and `connect()`.

**Why 8.8.8.8?** Unlike more complex operating systems, Zephyr does not automatically obtain DNS server addresses from DHCP. You must configure a DNS server explicitly. We use Google’s public DNS (8.8.8.8) as a reliable default that works on most networks. Alternatively, you could use Cloudflare’s 1.1.1.1 or your local network’s DNS server.

Also increase the stack and buffer sizes to accommodate HTTP operations:

```ini
# Stack sizes for ESP32 (increased for HTTP)
CONFIG_MAIN_STACK_SIZE=8192

# Network buffers (increased for HTTP)
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64

```

The complete `prj.conf` after these additions:

```ini
# Networking
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_TCP=y

# WiFi
CONFIG_WIFI=y
CONFIG_NET_L2_WIFI_MGMT=y
CONFIG_NET_MGMT=y
CONFIG_NET_MGMT_EVENT=y
CONFIG_NET_MGMT_EVENT_INFO=y

# Sockets
CONFIG_NET_SOCKETS=y
CONFIG_POSIX_API=y

# HTTP Client
CONFIG_HTTP_CLIENT=y

# DNS Resolver
CONFIG_DNS_RESOLVER=y
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="8.8.8.8"

# Stack sizes for ESP32
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_NET_TX_STACK_SIZE=2048
CONFIG_NET_RX_STACK_SIZE=2048

# Network buffers
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64

# Logging
CONFIG_LOG=y
CONFIG_NET_LOG=y

# WiFi credentials (edit these)
CONFIG_WIFI_SSID="YourSSID"
CONFIG_WIFI_PSK="YourPassword"

```

## HTTP GET Implementation

The HTTP client uses standard socket operations followed by Zephyr’s HTTP client helper. Update `src/main.c` with the HTTP request function.

Unlike Part 1 where we simply logged events and returned, here we need to **wait** for the IP address before making HTTP requests. We add a semaphore (`ip_obtained_sem`) that the IPv4 callback signals when DHCP completes, allowing `main()` to block until the network is ready.

We use [JSONPlaceholder](https://jsonplaceholder.typicode.com/) as our test endpoint - a free, public REST API designed for testing and prototyping. It requires no authentication, returns small JSON responses, and is reliably available. The `/todos/1` endpoint returns a simple JSON object, perfect for verifying our HTTP client works:

```c
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_event.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/http/client.h>
#include <zephyr/posix/netdb.h>
#include <zephyr/posix/unistd.h>
#include <zephyr/posix/sys/socket.h>

#include <errno.h>

LOG_MODULE_REGISTER(wifi_app, LOG_LEVEL_INF);

#define HTTP_HOST "jsonplaceholder.typicode.com"
#define HTTP_PORT "80"
#define HTTP_PATH "/todos/1"

static struct net_mgmt_event_callback wifi_cb;
static struct net_mgmt_event_callback ipv4_cb;
static K_SEM_DEFINE(ip_obtained_sem, 0, 1);

static uint8_t recv_buf[512];

static int response_cb(struct http_response *rsp,
                       enum http_final_call final_data,
                       void *user_data)
{
    if (final_data == HTTP_DATA_MORE) {
        LOG_INF("Partial data received (%zd bytes)", rsp->data_len);
    } else if (final_data == HTTP_DATA_FINAL) {
        LOG_INF("HTTP Status: %s", rsp->http_status);
        LOG_INF("Response body (%zd bytes):", rsp->data_len);
        LOG_INF("%.*s", (int)rsp->data_len, rsp->recv_buf);
    }

    return 0;
}

static int http_get_request(void)
{
    struct addrinfo hints;
    struct addrinfo *res;
    int sock;
    int ret;

    LOG_INF("Resolving %s...", HTTP_HOST);

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    ret = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res);
    if (ret != 0) {
        LOG_ERR("DNS lookup failed: %d", ret);
        return ret;
    }

    LOG_INF("DNS resolved, creating socket...");

    sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TCP);
    if (sock < 0) {
        LOG_ERR("Socket creation failed: %d", errno);
        freeaddrinfo(res);
        return -errno;
    }

    LOG_INF("Connecting to %s:%s...", HTTP_HOST, HTTP_PORT);

    ret = connect(sock, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    if (ret < 0) {
        LOG_ERR("Connect failed: %d", errno);
        close(sock);
        return -errno;
    }

    LOG_INF("Connected, sending HTTP GET %s...", HTTP_PATH);

    struct http_request req = {
        .method = HTTP_GET,
        .url = HTTP_PATH,
        .host = HTTP_HOST,
        .protocol = "HTTP/1.1",
        .response = response_cb,
        .recv_buf = recv_buf,
        .recv_buf_len = sizeof(recv_buf),
    };

    ret = http_client_req(sock, &req, 5 * MSEC_PER_SEC, NULL);
    if (ret < 0) {
        LOG_ERR("HTTP request failed: %d", ret);
    } else {
        LOG_INF("HTTP request completed (%d bytes sent)", ret);
    }

    close(sock);
    return ret;
}

/* WiFi event handlers from Part 1 */
static void wifi_event_handler(struct net_mgmt_event_callback *cb,
                               uint64_t mgmt_event, struct net_if *iface)
{
    if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) {
        LOG_INF("WiFi connected");
    } else if (mgmt_event == NET_EVENT_WIFI_DISCONNECT_RESULT) {
        LOG_INF("WiFi disconnected");
    }
}

static void ipv4_event_handler(struct net_mgmt_event_callback *cb,
                               uint64_t mgmt_event, struct net_if *iface)
{
    if (mgmt_event == NET_EVENT_IPV4_ADDR_ADD) {
        struct net_if_ipv4 *ipv4 = iface->config.ip.ipv4;

        if (ipv4) {
            char addr_str[NET_IPV4_ADDR_LEN];

            for (int i = 0; i < NET_IF_MAX_IPV4_ADDR; i++) {
                if (ipv4->unicast[i].ipv4.is_used) {
                    net_addr_ntop(AF_INET,
                                  &ipv4->unicast[i].ipv4.address.in_addr,
                                  addr_str, sizeof(addr_str));
                    LOG_INF("IP Address: %s", addr_str);
                }
            }
        }

        k_sem_give(&ip_obtained_sem);
    }
}

static int connect_wifi(void)
{
    struct net_if *iface = net_if_get_default();
    struct wifi_connect_req_params params = {
        .ssid = CONFIG_WIFI_SSID,
        .ssid_length = strlen(CONFIG_WIFI_SSID),
        .psk = CONFIG_WIFI_PSK,
        .psk_length = strlen(CONFIG_WIFI_PSK),
        .channel = WIFI_CHANNEL_ANY,
        .band = WIFI_FREQ_BAND_2_4_GHZ,
        .security = WIFI_SECURITY_TYPE_PSK,
    };

    LOG_INF("Connecting to %s...", CONFIG_WIFI_SSID);
    return net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &params, sizeof(params));
}

int main(void)
{
    int ret;

    LOG_INF("ESP32-S3 WiFi + HTTP Client Example");

    /* Register WiFi event callback */
    net_mgmt_init_event_callback(&wifi_cb, wifi_event_handler,
                                 NET_EVENT_WIFI_CONNECT_RESULT |
                                 NET_EVENT_WIFI_DISCONNECT_RESULT);
    net_mgmt_add_event_callback(&wifi_cb);

    /* Register IPv4 event callback */
    net_mgmt_init_event_callback(&ipv4_cb, ipv4_event_handler,
                                 NET_EVENT_IPV4_ADDR_ADD);
    net_mgmt_add_event_callback(&ipv4_cb);

    /* Wait for WiFi to initialize */
    k_sleep(K_SECONDS(2));

    /* Connect to WiFi */
    ret = connect_wifi();
    if (ret) {
        LOG_ERR("WiFi connect request failed: %d", ret);
        return ret;
    }

    /* Wait for IP address */
    LOG_INF("Waiting for IP address...");
    ret = k_sem_take(&ip_obtained_sem, K_SECONDS(30));
    if (ret < 0) {
        LOG_ERR("Timeout waiting for IP address");
        return ret;
    }

    /* Small delay to ensure network stack is ready */
    k_sleep(K_MSEC(500));

    /* Make HTTP request */
    ret = http_get_request();
    if (ret < 0) {
        LOG_ERR("HTTP GET failed: %d", ret);
    }

    return 0;
}

```

## How It Works

The `http_get_request()` function follows a standard socket workflow:

1. **DNS Resolution** : `getaddrinfo()` resolves the hostname to an IP address using the configured DNS server.

2. **Socket Creation** : `socket()` creates a TCP socket with `IPPROTO_TCP`.

3. **Connection** : `connect()` establishes the TCP connection to the server.

4. **HTTP Request** : Zephyr’s `http_client_req()` helper sends a properly formatted HTTP request and handles the response via callback.

5. **Cleanup** : `close()` terminates the connection.

The response callback `response_cb()` receives data as it arrives. For small responses, `HTTP_DATA_FINAL` indicates the complete response is available. Larger responses may arrive in chunks with `HTTP_DATA_MORE`.

## Building and Flashing

```bash
west build -b esp32s3_devkitc/esp32s3/procpu
west flash
west espressif monitor

```

## Expected Output

```auto
[00:00:00.000,000] <inf> wifi_app: ESP32-S3 WiFi + HTTP Client Example
[00:00:02.000,000] <inf> wifi_app: Connecting to YourSSID...
[00:00:02.000,000] <inf> wifi_app: Waiting for IP address...
[00:00:05.xxx,xxx] <inf> wifi_app: WiFi connected
[00:00:06.xxx,xxx] <inf> wifi_app: IP Address: 192.168.x.x
[00:00:06.xxx,xxx] <inf> wifi_app: Resolving jsonplaceholder.typicode.com...
[00:00:06.xxx,xxx] <inf> wifi_app: DNS resolved, creating socket...
[00:00:06.xxx,xxx] <inf> wifi_app: Connecting to jsonplaceholder.typicode.com:80...
[00:00:06.xxx,xxx] <inf> wifi_app: Connected, sending HTTP GET /todos/1...
[00:00:06.xxx,xxx] <inf> wifi_app: HTTP Status: 200 OK
[00:00:06.xxx,xxx] <inf> wifi_app: Response body (83 bytes):
[00:00:06.xxx,xxx] <inf> wifi_app: {
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}
[00:00:06.xxx,xxx] <inf> wifi_app: HTTP request completed (91 bytes sent)

```

## Next Steps

The HTTP connection works, but it is not encrypted. Anyone on the network can see the data in transit. Proceed to [Part 3: HTTPS with TLS](https://hub.mender.io/t/connectivity-with-zephyr-part-3-https-with-tls-on-esp32-s3/8132) to add proper encryption and certificate verification.

## Further Reading

- [Zephyr BSD Sockets API](https://docs.zephyrproject.org/latest/connectivity/networking/api/sockets.html) - socket interface documentation
- [Zephyr HTTP Client API](https://docs.zephyrproject.org/latest/connectivity/networking/api/http_client.html) - the `http_client_req()` function and related structures
- [Zephyr DNS Resolver](https://docs.zephyrproject.org/latest/connectivity/networking/api/dns_resolve.html) - DNS configuration and API
- [JSONPlaceholder API](https://jsonplaceholder.typicode.com/) - the free test API used in this tutorial
- [Zephyr Sockets Sample](https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/net/sockets/http_client) - official HTTP client sample code
