ESP32 HTTP server websocket streaming termination (PlatformIO + Ardurino espressif framework)

Stephen Cow Chau
5 min readDec 1, 2022

Background

Lately working on a microcontroller project to have an AI-Thinker ESP32-CAM to provide video streaming, plus allowing remote OTA update of firmware (by listening to MQTT message).

Given the limited resources on the ESP-32, I would like to shut down the HTTP server to release resources (mainly memory) before OTA happen.

There have been an observation that while there is an active streaming connection, the HTTP server cannot be stopped.

Environment / Setup

Project using PlatformIO with code base “ESP32 Arduino 2.0.5 based on ESP-IDF 4.4.2”

The project is running on Arduino framwork with PlatformIO (which I do have some regret on that)

Code base

The original camera web server code is based on the ESP32 Arduino example CameraWebServer:

The core code to start HTTP server is like following:

if (httpd_start(&stream_httpd, &config) == ESP_OK){
httpd_register_uri_handler(stream_httpd, &stream_uri);
}

While I assume the code to stop the HTTP server would be like following:

if(stream_httpd){
ESP_LOGI(TAG, "Try stopping stream server...");
httpd_unregister_uri(stream_httpd, "/stream");
if (httpd_stop(stream_httpd) == ESP_OK){
ESP_LOGI(TAG, "Stream server stopped");
}
else{
ESP_LOGE(TAG, "Stream server FAILED to stopped");
}
}

And as said above, the server does not stop while there is active streaming connection.

Attempt 1 (force kill the httpd)

After reviewing the source code (on github esp-idf project, because the arduino-esp32 project is precompiled and so there are only header files, while the underneath code are assumed to be from esp-idf roject), the httpd_start and httpd_stop would manage a thread named “httpd”

https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/httpd_main.c#L429

And that is translate into a freeRTOS task creation:

https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/port/esp32/osal.h#L25

So the first attempt is to find the task and kill it with following code:

handle = xTaskGetHandle( "httpd" );
if (handle){
vTaskDelete( handle );
}

And that result in a panic with error:

assert failed: lwip_netconn_do_writemore IDF/components/lwip/lwip/src/api/api_msg

So I assume there is assert checking before shutting down httpd. So I need to find out a way to diconnect the underneath socket connection before stopping the server.

Attempt 2 (figure out the socket and do the close connection)

Another important observation is that the streaming connection seems only allowing a single connection (not sure is because of resources constraint like limited memory or limited socket [16 max by default]), so the idea is to record down the socket number in the stream handler, and then close the connection when needed.

At the camera exmaple’s app_httpd.cpp, find out that stream_handler code and calling the httpd_req_tosockfd(req) would return the request’s socket ID (the stream_socket_id is placed in global scope for being lazy)

At first, I naively think that the number should be 0–15 (as there are only 16 sockets) so that I can just loop the numbers and close all sockets, but I am wrong, in my code, my observation of the socket ID is from some range around 56~60.

There is also a way to observe the socket number by adding some open_fn and close_fn in config like following (commented):

The code to close socket:

ESP_LOGI(OTA_TAG, “close socket #%d…”, stream_socket_id);
closesocket(stream_socket_id);

after this close socket, the httpd_stop call can shutdown successfully.

Further study — how the httpd store the socket information?

Taken for granted for all networking layer would work fine at high level application programming (e.g. web / app programming with JavaScript, Python, Kotlin…), this ESP32 exercise reminded me on OSI network layers. HTTP is at layer7, the socket is at layer5 (plus alot of common problems on esp-tls and mbedtls on layer 6 and the error tracing down to lwip at layer 4 and tcp_transport at layer 3…).

From esp_http_server componet, there is no direct function call exposed to access the socket. But at some point in the source code, I see the pointer type casting between the httpd_handle_t (which we get back from httpd_start) and httpd_data (which is the structure to hold all necessary information, including the socket information).

Like below, the httpd_data* hd is returned from httpd_create(config) and then before return, we have the *handle = (httpd_handle_t *) hd;

https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/httpd_main.c#L439

And from with in the httpd_create function, we see it allocate memory for some sock_db:

https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/httpd_main.c#L350

The sock_db is the structure to keep track of all open sockets (for the httpd), and defined as follow:

https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/esp_httpd_priv.h#L52

So I guess it would be safer to perform some code like below to close all sockets:

// !!! This code is not tested !!!
// Code Ref: https://github.com/espressif/esp-idf/blob/v4.4.2/components/esp_http_server/src/httpd_main.c#L102
struct httpd_data *hd = (httpd_data *) stream_httpd;
size_t max_fds = *fds;
for (int i = 0; i < hd->config.max_open_sockets; ++i) {
if (hd->hd_sd[i].fd != -1) {
if (*fds < max_fds) {
closesocket(hd->hd_sd[i].fd);
}
}
}

Conclusion

I wish more people is working on microcontroller projects and share their experience and findings so that other developers can benefit from.

My solution is not likely align with best practice, especially when picking up C++ 20 years after I put it down.

Also, most of the sharing are people using esp-idf, which is one of the reason I regret using Arduino framework in PlatformIO, as most of the solution require using menuconfig to configure the sdkconfig and so change the compilation of the esp-idf library (which, in arduino-esp32 library, all esp-idf components are precompiles and so most solution are not applicable).

--

--