Skip to content

Websocket multipart message does not work as expected #382

@LSC-Labs

Description

@LSC-Labs

Platform

ESP8266

IDE / Tooling

PlatformIO

What happened?

I tried to send from the browser a json file (size about 1k) via web socket to the module.
I installed a new environment to avoid side effects and I used the code from the exsample folder: "WebSocket.ino"

The code is working with single frames and works as expected...
Then I inserted the code from https://github.com/ESP32Async/ESPAsyncWebServer/wiki#async-websocket-event to get also the
multipart message to receive the large messages.

  • The first message is received as expected... but with final set to true (!)
  • The second message will also be received.. also with final set to true and index set to 0, the data pointer contains garbage
    Output is (i inserted framelen with the parameter len to visualize it):
Connected clients: 1 / 1 total
index: 0, len: 1447, final: 1, opcode: 1, framelen: 528
ws[/ws][4] frame[0] text[0 - 4]: ws[/ws][5] text-message start
ws[/ws][5] frame[0] start[1447]
{     "logToSerial": true,     "traceMode": false,     "devicename": "LSC-device",     "devicepwd": "admin-geheim",     "autorestart": "86400",          "wifi": {         "ap_channel": 3,         "ap_hide": false,         "bssid": "aa:bb:33:22:11:fe",         "ap_ssid": "LSC-8eaa2c44",         "ap_mode": "0",         "dhcp": true,         "fallback": false,         "ssid": "WiFiOnICE",         "wifi_pwd": "JoJoBaOel",         "hostname": "LSC-Device"        },     "dewswitch": {         "dewdelta": 5.2,         "hysteresis
index: 0, len: 1447, final: 1, opcode: 1, framelen: 532
ws[/ws][5] frame[0] text[0 - 528]: ws[/ws][5] text-message start
ws[/ws][5] frame[0] start[1447]



index: 0, len: 1447, final: 1, opcode: 1, framelen: 379
ws[/ws][5] frame[0] text[0 - 532]: ws[/ws][5] text-message start
ws[/ws][5] frame[0] start[1447]
VSM
Connected clients: 1 / 1 total
  • the next click with data "dd" produces:
ndex: 0, len: 1447, final: 1, opcode: 1, framelen: 4
ws[/ws][5] frame[0] text[0 - 379]: ws[/ws][5] text-message start
ws[/ws][5] frame[0] start[1447]
��dd
  • here the data is corrupted...

I'm using

  • a D1 mini (ESP8266)
  • PlatformIO
  • Visual Code
  • MacOS (M4) 26.2 (25C56)
  • Using this libraries:
    Dependency Graph
    |-- ESP8266WiFi @ 1.0
    |-- ESPAsyncTCP @ 2.0.0
    |-- ESPAsyncWebServer @ 3.9.6
    Building in debug mode

Thanks

Peter

Stack Trace

No Stack Trace, cause the application did not crash..

Minimal Reproductible Example (MRE)

Source code:

// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles

//
// WebSocket example
//

#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif

#include <ESPAsyncWebServer.h>

static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
  <title>WebSocket</title>
</head>
<body>
  <h1>WebSocket Example</h1>
  <p>Open your browser console!</p>
  <input type="text" id="message" placeholder="Type a message">
  <button onclick='sendMessage()'>Send</button>
  <script>
    var ws = new WebSocket('ws://192.168.4.1/ws');
    ws.onopen = function() {
      console.log("WebSocket connected");
    };
    ws.onmessage = function(event) {
      console.log("WebSocket message: " + event.data);
    };
    ws.onclose = function() {
      console.log("WebSocket closed");
    };
    ws.onerror = function(error) {
      console.log("WebSocket error: " + error);
    };
    function sendMessage() {
      var message = document.getElementById("message").value;
      ws.send(message);
      console.log("WebSocket sent: " + message);
    }
  </script>
</body>
</html>
  )";
static const size_t htmlContentLength = strlen_P(htmlContent);

static AsyncWebServer server(80);
static AsyncWebSocket ws("/ws");

void setup() {
  Serial.begin(115200);

#if ASYNCWEBSERVER_WIFI_SUPPORTED
  WiFi.mode(WIFI_AP);
  WiFi.softAP("esp-captive");
#endif

  // serves root html page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
  });

  //
  // Run in terminal 1: websocat ws://192.168.4.1/ws => should stream data
  // Run in terminal 2: websocat ws://192.168.4.1/ws => should stream data
  // Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
  //
  // To send a message to the WebSocket server:
  //
  // echo "Hello!" | websocat ws://192.168.4.1/ws
  //
  ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
    (void)len;

    if (type == WS_EVT_CONNECT) {
      ws.textAll("new client connected");
      Serial.println("ws connect");
      client->setCloseClientOnQueueFull(false);
      client->ping();

    } else if (type == WS_EVT_DISCONNECT) {
      ws.textAll("client disconnected");
      Serial.println("ws disconnect");

    } else if (type == WS_EVT_ERROR) {
      Serial.println("ws error");

    } else if (type == WS_EVT_PONG) {
      Serial.println("ws pong");

    } else if (type == WS_EVT_DATA) {
      AwsFrameInfo *info = (AwsFrameInfo *)arg;
      Serial.printf("index: %" PRIu64 ", len: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", framelen: %d\n", 
        info->index, 
        info->len, 
        info->final, 
        info->opcode,
        len
    );
      String msg = "";
      if (info->final && info->index == 0 && info->len == len) {
        if (info->opcode == WS_TEXT) {
          data[len] = 0;
          Serial.printf("ws text: %s\n", (char *)data);
          client->ping();
        }
      } else {
        if(info->index == 0){
        if(info->num == 0)
          os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
        os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
      }

      os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
      if(info->message_opcode == WS_TEXT){
        data[len] = 0;
        os_printf("%s\n", (char*)data);
      } else {
        for(size_t i=0; i < len; i++){
          os_printf("%02x ", data[I]);
        }
        os_printf("\n");
      }

      if((info->index + len) == info->len){
        os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
        if(info->final){
          os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
          if(info->message_opcode == WS_TEXT)
            client->text("I got your text message");
          else
            client->binary("I got your binary message");
        }
      }
      }
    }
  });

  // shows how to prevent a third WS client to connect
  server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
    // ws.count() is the current count of WS clients: this one is trying to upgrade its HTTP connection
    if (ws.count() > 1) {
      // if we have 2 clients or more, prevent the next one to connect
      request->send(503, "text/plain", "Server is busy");
    } else {
      // process next middleware and at the end the handler
      next();
    }
  });

  server.addHandler(&ws);

  server.begin();
}

static uint32_t lastWS = 0;
static uint32_t deltaWS = 100;

static uint32_t lastHeap = 0;

void loop() {
  uint32_t now = millis();

  if (now - lastWS >= deltaWS) {
    ws.printfAll("kp%.4f", (10.0 / 3.0));
    lastWS = millis();
  }

  if (now - lastHeap >= 2000) {
    Serial.printf("Connected clients: %u / %u total\n", ws.count(), ws.getClients().size());

    // this can be called to also set a soft limit on the number of connected clients
    ws.cleanupClients(2);  // no more than 2 clients

#ifdef ESP32
    Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
#endif
    lastHeap = now;
  }
}

Test data:

{
    "logToSerial": true,
    "traceMode": false,
    "devicename": "LSC-device",
    "devicepwd": "admin-geheim",
    "autorestart": "86400",
    
    "wifi": {
        "ap_channel": 3,
        "ap_hide": false,
        "bssid": "aa:bb:33:22:11:fe",
        "ap_ssid": "LSC-8eaa2c44",
        "ap_mode": "0",
        "dhcp": true,
        "fallback": false,
        "ssid": "WiFiOnICE",
        "wifi_pwd": "JoJoBaOel",
        "hostname": "LSC-Device"   
    },
    "dewswitch": {
        "dewdelta": 5.2,
        "hysteresis": 1.5,
        "min_outdoor": -10.3,
        "min_indoor": 10.1
    },
    "sensor": {
        "adjust_humidity": 5,
        "adjust_temp": -0.2,
        "physical": true,
        "web_key": "33445566778899abcdef",
        "web_lat": "23.556778",
        "web_long": "8.553641"
    },
    "sensorOD": {
        "adjust_humidity": 0.3,
        "adjust_temp": 0.5,
        "physical": false,
        "web_key": "33445566778899abcdef",
        "web_lat": "23.556778",
        "web_long": "8.553641"
    },
    "mqtt" : {
        "enabled": false,
        "autotopic": false,
        "host": "mqtt-host",
        "port": 1883,
        "syncrate": 60,
        "topic": "",
        "user": "",
        "passwd": "",
        "useha": false
    },
    "rf433": {
        "enabled": true,
        "msgs": [
            { "on": 4333356,"msg": 1, "type":1 },
            { "on": 4333362,"msg": 0, "type":0 }
        ]
        }
}

I confirm that:

  • I have read the documentation.
  • I have searched for similar discussions.
  • I have searched for similar issues.
  • I have looked at the examples.
  • I have upgraded to the lasted version of ESPAsyncWebServer (and AsyncTCP for ESP32).

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions