#pragma once #include #include #include #include #include #include #include #include #include #include #define HTTP_CODE_OK 200 #define HTTP_CODE_NOT_FOUND 404 class CWebServer { private: // Template for param to value converter function, used by PushPostParamIfPresent() template using ParamValueGetter = std::function; // Template for value setting forwarding function, used by PushPostParamIfPresent() template using ValueSetter = std::function; // Value validating function type, as used by DeviceConfig (and possible others) using ValueValidator = std::function; struct StaticStatistics { uint32_t HeapSize; size_t DmaHeapSize; uint32_t PsramSize; const char *ChipModel; uint8_t ChipCores; uint32_t CpuFreqMHz; uint32_t SketchSize; uint32_t FreeSketchSpace; uint32_t FlashChipSize; }; static std::vector deviceSettingSpecs; static const std::map settingValidators; AsyncWebServer _server; StaticStatistics _staticStats; #if USE_WEBSOCKET AsyncWebSocket _ws; #endif // Helper functions/templates // Convert param value to a specific type and forward it to a setter function that expects that type as an argument template static bool PushPostParamIfPresent(AsyncWebServerRequest * pRequest, const String ¶mName, ValueSetter setter, ParamValueGetter getter) { if (!pRequest->hasParam(paramName, true, false)) return false; log_v("found %s", paramName.c_str()); AsyncWebParameter *param = pRequest->getParam(paramName, true, false); // Extract the value and pass it off to the setter return setter(getter(param)); } // Generic param value forwarder. The type argument must be implicitly convertable from String! // Some specializations of this are included in the CPP file template static bool PushPostParamIfPresent(AsyncWebServerRequest * pRequest, const String ¶mName, ValueSetter setter) { return PushPostParamIfPresent(pRequest, paramName, setter, [](AsyncWebParameter * param) { return param->value(); }); } // AddCORSHeaderAndSend(OK)Response // // Sends a response with CORS headers added template static void AddCORSHeaderAndSendResponse(AsyncWebServerRequest * pRequest, Tr * pResponse) { // pResponse->addHeader("Server", HOSTNAME); // pResponse->addHeader("Access-Control-Allow-Origin", "*"); pRequest->send(pResponse); } // Version for empty response, normally used to finish up things that don't return anything, like "NextEffect" static void AddCORSHeaderAndSendOKResponse(AsyncWebServerRequest * pRequest) { AddCORSHeaderAndSendResponse(pRequest, pRequest->beginResponse(HTTP_CODE_OK)); } // Straightforward support functions static bool IsPostParamTrue(AsyncWebServerRequest * pRequest, const String & paramName); static const std::vector & LoadDeviceSettingSpecs(); static void SendSettingSpecsResponse(AsyncWebServerRequest * pRequest, const std::vector & settingSpecs); static void SetSettingsIfPresent(AsyncWebServerRequest * pRequest); #if USE_OAT static void UpdateFirmware(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); static void UpdateFileSystemImage(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); static void HandleUpdate(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final, size_t upload_size, uint8_t upload_index); #endif // Endpoint member functions static void GetSettingSpecs(AsyncWebServerRequest * pRequest); static void GetSettings(AsyncWebServerRequest * pRequest); static void SetSettings(AsyncWebServerRequest * pRequest); static void SaveFile(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); #if USE_OAT static void UpdateRequestHandler(AsyncWebServerRequest * pRequest); #endif static void Reset(AsyncWebServerRequest * pRequest); static void GzipSpa(AsyncWebServerRequest * pRequest); static void HandleWsMessage(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); // Not static because it uses member _staticStats void GetStatistics(AsyncWebServerRequest * pRequest); public: CWebServer() : _server(HTTP_PORT) #if USE_WEBSOCKET , _ws(WEBSOCKET_PATH) #endif {} void begin() { DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type"); DefaultHeaders::Instance().addHeader("Server", HOSTNAME); _staticStats.HeapSize = ESP.getHeapSize(); _staticStats.DmaHeapSize = heap_caps_get_total_size(MALLOC_CAP_DMA); _staticStats.PsramSize = ESP.getPsramSize(); _staticStats.ChipModel = ESP.getChipModel(); _staticStats.ChipCores = ESP.getChipCores(); _staticStats.CpuFreqMHz = ESP.getCpuFreqMHz(); _staticStats.SketchSize = ESP.getSketchSize(); _staticStats.FreeSketchSpace = ESP.getFreeSketchSpace(); _staticStats.FlashChipSize = ESP.getFlashChipSize(); _server.onFileUpload(SaveFile); #if USE_WEBSOCKET _ws.onEvent(HandleWsMessage); _server.addHandler(&_ws); #endif _server.on("/api/statistics", HTTP_GET, [this](AsyncWebServerRequest * pRequest) { this->GetStatistics(pRequest); }); _server.on("/api/getStatistics", HTTP_GET, [this](AsyncWebServerRequest * pRequest) { this->GetStatistics(pRequest); }); _server.on("/api/settings/specs", HTTP_GET, [](AsyncWebServerRequest * pRequest) { GetSettingSpecs(pRequest); }); _server.on("/api/settings", HTTP_GET, [](AsyncWebServerRequest * pRequest) { GetSettings(pRequest); }); _server.on("/api/settings", HTTP_POST, [](AsyncWebServerRequest * pRequest) { SetSettings(pRequest); }); #if USE_OAT _server.on("/api/update/filesystem", HTTP_POST, UpdateRequestHandler, UpdateFirmware); _server.on("/api/update/firmware", HTTP_POST, UpdateRequestHandler, UpdateFileSystemImage); #endif _server.on("/api/stream", HTTP_GET, streamJpg); _server.on("/api/reset", HTTP_POST, [](AsyncWebServerRequest * pRequest) { Reset(pRequest); }); _server.serveStatic("/", SPIFFS, "/").setCacheControl("max-age=31536000"); _server.onNotFound([](AsyncWebServerRequest *pRequest) { GzipSpa(pRequest); }); #if USE_WEBSERVER_SSL _server.beginSecure("/server.cer", "/server.key", NULL); _server.onSslFileRequest([](void * arg, const char *filename, uint8_t **buf) -> int { Serial.printf("SSL File: %s\n", filename); File file = SPIFFS.open(filename, "r"); if(file){ size_t size = file.size(); uint8_t * nbuf = (uint8_t*)malloc(size); if(nbuf){ size = file.read(nbuf, size); file.close(); *buf = nbuf; return size; } file.close(); } *buf = 0; return 0; }, NULL); #else _server.begin(); #endif log_i("HTTP server started"); } void loop() { #if USE_WEBSOCKET _ws.cleanupClients(); #endif } void broadcast(uint8_t* content, size_t length) { _ws.binaryAll(content, length); } void broadcastJson(char* content, size_t length) { _ws.textAll(content, length); } }; // Set value in lambda using a forwarding function. Always returns true #define SET_VALUE(functionCall) [](auto value) { functionCall; return true; } // Set value in lambda using a forwarding function. Reports success based on function's return value, // which must be implicitly convertable to bool #define CONFIRM_VALUE(functionCall) [](auto value)->bool { return functionCall; }