Skip to content

Overview

HTTP is the most common protocol for IoT devices talking to servers; GET fetches resources from the server (web pages, weather data, firmware version numbers, etc.). The board completes a GET request by sending an HTTP message directly over a socket (the network's "power outlet" — the channel through which programs send/receive data): DNS resolution → TCP connection → send request → read response. This tutorial demonstrates: accessing the root path of example.com, printing the returned web page character by character to the serial, then a 10-second countdown before requesting again.

In plain words: HTTP is like ordering food at a restaurant — you (the client) say "I want this" (the request), and the waiter (the server) brings the dish (the response). GET is "take": ask the server for a document (web page/data), and the server sends the content back. The whole process: first translate the shop name example.com into a street number (DNS resolution), dial the phone (TCP connection), then say "give me the menu" (send the request), and finally receive the dish (read the response).

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/protocols/http_client_socket; the code can be found directly in your local SDK.

🎯Page GoalComplete an HTTP GET request over a raw socket, mastering the full flow of DNS resolution, request-message construction and response reading.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② A 2.4GHz router (with public internet access) ③ Environment set up per [SDK Installation](../../sdk/sdk_intro) and completed [Connect Wi-Fi](./wifi_connect).
🔗RelatedTCP fundamentals: [TCP Client](./tcp_client); submitting data to a server: [HTTP POST](./http_post).

Enter the Example Project

Open a terminal and enter the official http_client_socket example project directory:

cd ~/Ai-Thinker-WB2/applications/protocols/http_client_socket

Note: cd is the “change directory” command, entering the official example project; all subsequent make commands must run in this directory.

Modify the Router Parameters and Target Address

Open http_client_socket/main.c and change the SSID/password at the top (the official default is AIOT@FAE — change it to your own router).

Open http_client_socket/demo.c and change the target server:

#define WEB_SERVER "example.com"
#define WEB_PORT "80"
#define WEB_PATH "/"
Macro Default Description
WEB_SERVER example.com The server’s domain name (an easy-to-remember name like example.com; the code resolves it to an IP)
WEB_PORT 80 HTTP port (80 is the default)
WEB_PATH / Request path (e.g. /api/weather)

💡 You can also use any HTTP site (e.g. httpbin.org, api.ipify.org) or a LAN server (run an HTTP service on your computer). Note: only http:// is supported — for https sites see the official https_mbedtls example.

Write the Code

Open demo.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/protocols/http_client_socket/http_client_socket/demo.c).

Code highlights:

Code Purpose
REQUEST static string The pre-assembled “order form” handed to the server (request line + headers + blank line); if the format is wrong the server won’t recognize it
getaddrinfo(WEB_SERVER, "80", &hints, &res) Translates the domain name to an IP; if the translation fails the server can’t be found, so retry
socket(res->ai_family, res->ai_socktype, 0) Creates the “telephone line” (socket); without it there’s no communication
connect(s, res->ai_addr, res->ai_addrlen) Dials the server’s port 80; if it doesn’t connect, the address or network is wrong
write(s, REQUEST, strlen(REQUEST)) Hands the “menu” to the server — this step truly sends the request
setsockopt(SO_RCVTIMEO, 5s) Sets a 5-second timeout so the program never hangs when the server doesn’t respond
read(s, recv_buf, ...) loop Loops reading the response until nothing is left (the server closed the connection); only then is the content complete
bl_putchar(recv_buf[i]) Prints the received content character by character to the serial for easy viewing
failure branch continue + countdown Auto-retry on failure; on success wait 10 seconds for the next round, simulating periodic reporting

💡 An HTTP/1.0 request message has three parts: the request line GET / HTTP/1.0 (method + path + version), headers (Host required, User-Agent optional), and a trailing blank line. The server returns a status line (e.g. HTTP/1.1 200 OK) + response headers + response body (the page HTML).

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program flashed into the board) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/http_client_socket.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board. After p= comes the serial device (often /dev/ttyUSB0 on Linux, COM3-like on Windows — use your computer’s actual one), b=921600 is the flash baud rate (serial transfer speed), keep the default.

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.

Run and Verify

After flashing, the board automatically restarts and runs. The serial (baud rate 921600) prints:

[APP] [EVT] GOT IP 5594
DNS lookup succeeded. IP=93.184.216.34
... allocated socket
... connected
... socket send success
... set socket receiving timeout success
<!doctype html>
<html>
<head>...(the complete page HTML returned by example.com, printed character by character)
</html>
... done reading from socket. Last read return=0 errno=0
10... 9... 8... ...
0...
Starting again!

Seeing DNS lookup succeeded, connected and the page HTML means success; if you only see GOT IP without DNS lookup succeeded, DNS resolution failed (the board isn’t online or the router can’t reach the public internet) — see the FAQ at the end. Before verifying, confirm the board got GOT IP (online) and that your computer’s browser can open http://example.com (the target server is reachable). The program runs in a loop (one round every 10 seconds).

💡 Enable auto line-break/timestamps in the serial assistant for easier observation; if the page content is large, wait patiently for the printing to finish.


API Summary for This Tutorial

getaddrinfo(name, port, &hints, &res)

Resolves a domain name into a linked list of IPs (struct addrinfo).

Parameters:

  • name: domain or IP string, e.g. "example.com"
  • port: port string, e.g. "80"
  • hints: query criteria structure (limits results to IPv4 + TCP, avoiding IPv6/other types)
  • res: output parameter, resolved linked-list pointer

Return: 0 on success; negative error code on failure

freeaddrinfo(res)

Frees the getaddrinfo result.

Parameters:

  • res: result linked list returned by getaddrinfo

Return: none

socket(af, type, proto)

Creates a TCP socket.

Parameters:

  • af: address family, AF_INET (IPv4)
  • type: socket type, SOCK_STREAM (streaming TCP)
  • proto: protocol, pass 0 (auto-selected)

Return: socket descriptor on success; -1 on failure

connect(fd, addr, addrlen)

Connects to the server.

Parameters:

  • fd: socket descriptor
  • addr: server address (struct sockaddr*, pointing to sockaddr_in)
  • addrlen: address length sizeof(struct sockaddr_in)

Return: 0 on success; negative error code on failure

write(fd, buf, len)

Sends the request message.

Parameters:

  • fd: socket descriptor
  • buf: request message buffer
  • len: message length

Return: bytes sent on success; negative on failure

read(fd, buf, len)

Reads the server response.

Parameters:

  • fd: socket descriptor
  • buf: receive buffer
  • len: buffer length

Return: bytes read; 0 = peer closed; negative = error

close(fd)

Closes the connection.

Parameters:

  • fd: socket descriptor

Return: 0 on success; -1 on failure

setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, len)

Sets the receive timeout (struct timeval: seconds + microseconds).

Parameters:

  • fd: socket descriptor
  • level: option layer, SOL_SOCKET
  • optname: option name, SO_RCVTIMEO (receive timeout)
  • tv: struct timeval pointer, e.g. {5, 0} means 5 seconds
  • len: sizeof(struct timeval)

Return: 0 on success; negative error code on failure

inet_ntoa(addr)

Converts an IP address to a dotted-decimal string (not thread-safe).

Parameters:

  • addr: network-byte-order IP address (in_addr)

Return: dotted-decimal string, e.g. "93.184.216.34"

bzero(buf, len)

Zeroes a buffer (same as memset(buf, 0, len)).

Parameters:

  • buf: target buffer
  • len: bytes to zero

Return: none

bl_putchar(c)

Outputs one character to the serial (extern declaration in demo.c, implemented in the SDK platform layer).

Parameters:

  • c: the character to output

Return: none

📌 struct addrinfo key fields: ai_family (AF_INET), ai_socktype (SOCK_STREAM), ai_addr (server address), ai_addrlen (address length). hints limits results to IPv4 + TCP, avoiding IPv6/other types being resolved at the same time.


Full Code

Below is the complete demo.c source, identical to the official example (applications/protocols/http_client_socket/http_client_socket/demo.c):

📜 Click to expand the full demo.c code
c
#include <stdio.h>
#include <FreeRTOS.h>
#include <task.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/tcp.h>
#include <lwip/err.h>
#include <http_client.h>
#include <cli.h>
#include "demo.h"
#include <blog.h>

#define WEB_SERVER "example.com"
#define WEB_PORT "80"
#define WEB_PATH "/"

static const char *REQUEST = "GET " WEB_PATH " HTTP/1.0\r\n"
                             "Host: " WEB_SERVER ":" WEB_PORT "\r\n"
                             "User-Agent: aithinker wb2\r\n"
                             "\r\n";

void http_get_task(void *pvParameters)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s, r;
    char recv_buf[4096];

    while (1)
    {
        int err = getaddrinfo(WEB_SERVER, "80", &hints, &res);

        if (err != 0 || res == NULL)
        {
            blog_error("DNS lookup failed err=%d res=%p", err, res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }

        /* Code to print the resolved IP.
           Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
        blog_info("DNS lookup succeeded. IP=%s", inet_ntoa(*addr));

        s = socket(res->ai_family, res->ai_socktype, 0);
        if (s < 0)
        {
            blog_error("... Failed to allocate socket.");
            freeaddrinfo(res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }
        blog_info("... allocated socket");

        if (connect(s, res->ai_addr, res->ai_addrlen) != 0)
        {
            blog_error("... socket connect failed errno=%d", errno);
            close(s);
            freeaddrinfo(res);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }

        blog_info("... connected");
        freeaddrinfo(res);

        if (write(s, REQUEST, strlen(REQUEST)) < 0)
        {
            blog_error("... socket send failed");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        blog_info("... socket send success");

        struct timeval receiving_timeout;
        receiving_timeout.tv_sec = 5;
        receiving_timeout.tv_usec = 0;
        if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
                       sizeof(receiving_timeout)) < 0)
        {
            blog_error("... failed to set socket receiving timeout");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        blog_info("... set socket receiving timeout success");

        // FIXME fix putchar
        extern int bl_putchar(int c);

        /* Read HTTP response */
        do
        {
            bzero(recv_buf, sizeof(recv_buf));
            r = read(s, recv_buf, sizeof(recv_buf) - 1);
            for (int i = 0; i < r; i++)
            {
                bl_putchar(recv_buf[i]);
            }
        } while (r > 0);

        blog_info("... done reading from socket. Last read return=%d errno=%d\r\n", r, errno);
        close(s);
        for (int countdown = 10; countdown >= 0; countdown--)
        {
            blog_info("%d... ", countdown);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
        blog_info("Starting again!");
    }
}

FAQ & Troubleshooting

⚠️ Keeps printing DNS lookup failed
Cause: the router isn't online, the DNS server is unreachable, or the domain is misspelled
Fix: confirm the board can reach the public internet (other examples can GOT IP and go online); check the WEB_SERVER domain is correct; restart the router

⚠️ connect failed then keeps retrying
Cause: wrong server port (not 80), unreachable target, or the router firewall blocks it
Fix: confirm WEB_PORT "80" matches the server; for a LAN server, first test from the computer that it's accessible; note only http:// is supported

⚠️ Page printing stops halfway
Cause: the 5-second receive timeout expired (slow server response or a large page)
Fix: increase receiving_timeout.tv_sec (e.g. 10); or use a faster-responding site

⚠️ Stack overflow crash after modifying the code
Cause: recv_buf[4096] lives on the task stack, which must be large enough
Fix: keep the official task stack 16384 (xTaskCreate(&http_get_task, "http_get_task", 16384, NULL, 5, NULL)), don't shrink it

⚠️ Accessing https:// sites fails
Cause: this example is plaintext HTTP; HTTPS requires a TLS handshake
Fix: use an http:// site; for HTTPS see the official https_mbedtls example (the later chapter MQTTS also involves TLS)

⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: double-check the SSID/password in main.c; confirm the router broadcasts 2.4GHz; move the board closer to the router; you can run Connect Wi-Fi alone first to verify networking

⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable

⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable

Self-Check

The serial shows DNS lookup succeededconnected → the full page HTML printed — the HTTP GET is verified.

Released under the MIT License. Build Time 2026-09-11 14:52:23