me-no-dev / ESPAsyncWebServer

Async Web Server for ESP8266 and ESP32
3.68k stars 1.21k forks source link

Web server hangs up #1425

Open NicklasMatzulla opened 1 month ago

NicklasMatzulla commented 1 month ago

Hello everyone, I have written a Rest API for an OTA update. Basically, this works great, but as soon as I execute the request#send method during the upload process (several times), the web server hangs and no new requests are accepted and existing ones. If I remove all request#send calls, nothing hangs anymore. How can I make the web server report the upload status to the client but not hang up? Unfortunately, I have not found an explanation for this in the sample code.

#include "RestApi.h"
#include <Update.h>
#include <ArduinoJson.h>

#define U_PART U_SPIFFS

const size_t MAX_FIRMWARE_SIZE = 1048576; // 1 MB (ota partition size)
const size_t MAX_SPIFFS_SIZE = 786432; // 0.78 mb (spiffs partition size)
AsyncWebServerRequest* updateRequest = nullptr;

bool endsWith(const String& str, const String& suffix) {
  if (str.length() >= suffix.length()) {
    return str.substring(str.length() - suffix.length()) == suffix;
  } else {
    return false;
  }
}

void handleFirmwareUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final, bool updateUi) {
  if (updateRequest == nullptr || updateRequest == request) {
    size_t firmwareSize = request->contentLength();
    size_t allowedFirmwareSize = updateUi ? MAX_SPIFFS_SIZE : MAX_FIRMWARE_SIZE;
    if (!endsWith(filename, ".bin") || firmwareSize > allowedFirmwareSize) {
      request->send(400, "application/json", "{\"message\":\"Invalid file or file size too large.\"}");
      return;
    }
    if (!index) {
      updateRequest = request;
      Serial.println("Processing firmware update...");
      size_t content_len = request->contentLength();
      int cmd = updateUi ? U_PART : U_FLASH;
      if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) {
        String errorMsg = "Update begin error: " + String(Update.errorString());
        Serial.println(errorMsg);
        request->send(500, "application/json", "{\"message\":\"" + errorMsg + "\"}");
        updateRequest = nullptr;
        Update.end();
        return;
      }
    }
    if (Update.write(data, len) != len) {
      String errorMsg = "Update write error: " + String(Update.errorString());
      Serial.println(errorMsg);
      request->send(500, "application/json", "{\"message\":\"" + errorMsg + "\"}");
      updateRequest = nullptr;
      Update.end();
      return;
    }
    if (final) {
      if (Update.end(true)) {
        Serial.println("Firmware update complete.");
        request->send(200, "application/json", "{\"message\":\"Firmware update complete.\"}");
      } else {
        String errorMsg = "Update end error: " + String(Update.errorString());
        Serial.println(errorMsg);
        request->send(500, "application/json", "{\"message\":\"" + errorMsg + "\"}");
      }
      updateRequest = nullptr;
    }
  } else {
    request->send(400, "application/json", "{\"message\":\"Another update is in progress.\"}");
  }
}

void setupRestApi(AsyncWebServer &server) {
  server.on("/api/v1/firmware/update", HTTP_POST, [](AsyncWebServerRequest *request) {},
  [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
    handleFirmwareUpload(request, filename, index, data, len, final, false);
  });
  server.on("/api/v1/ui/update", HTTP_POST, [](AsyncWebServerRequest *request) {},
  [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
    handleFirmwareUpload(request, filename, index, data, len, final, true);
  });
}
butaikis commented 1 month ago

Look at the Update class, there is an opportunity to put a callback on &onProgress(THandlerFunction_Progress fn); with it, you can track the file upload process and save data to the global area. Then use the web server to return the data to the user.