/* WEBSERVER MODULE Copyright (C) 2016-2017 by Xose PĂ©rez */ #if WEB_SUPPORT #include #include #include #include #include #include #include #if WEB_EMBEDDED #include "static/index.html.gz.h" #endif // WEB_EMBEDDED #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED #include "static/server.cer.h" #include "static/server.key.h" #endif // ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED // ----------------------------------------------------------------------------- AsyncWebServer * _server; char _last_modified[50]; Ticker _web_defer; // ----------------------------------------------------------------------------- // HOOKS // ----------------------------------------------------------------------------- void _onGetConfig(AsyncWebServerRequest *request) { webLog(request); if (!_authenticate(request)) return request->requestAuthentication(); AsyncJsonResponse * response = new AsyncJsonResponse(); JsonObject& root = response->getRoot(); root["app"] = APP_NAME; root["version"] = APP_VERSION; unsigned int size = settingsKeyCount(); for (unsigned int i=0; iaddHeader("Content-Disposition", buffer); response->setLength(); request->send(response); } #if WEB_EMBEDDED void _onHome(AsyncWebServerRequest *request) { webLog(request); if (!_authenticate(request)) return request->requestAuthentication(); if (request->header("If-Modified-Since").equals(_last_modified)) { request->send(304); } else { #if ASYNC_TCP_SSL_ENABLED // Chunked response, we calculate the chunks based on free heap (in multiples of 32) // This is necessary when a TLS connection is open since it sucks too much memory DEBUG_MSG_P(PSTR("[MAIN] Free heap: %d bytes\n"), ESP.getFreeHeap()); size_t max = (ESP.getFreeHeap() / 3) & 0xFFE0; AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [max](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { // Get the chunk based on the index and maxLen size_t len = index_html_gz_len - index; if (len > maxLen) len = maxLen; if (len > max) len = max; if (len > 0) memcpy_P(buffer, index_html_gz + index, len); DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / index_html_gz_len), max); if (len == 0) DEBUG_MSG_P(PSTR("\n")); // Return the actual length of the chunk (0 for end of file) return len; }); #else AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html_gz, index_html_gz_len); #endif response->addHeader("Content-Encoding", "gzip"); response->addHeader("Last-Modified", _last_modified); request->send(response); } } #endif #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED int _onCertificate(void * arg, const char *filename, uint8_t **buf) { #if WEB_EMBEDDED if (strcmp(filename, "server.cer") == 0) { uint8_t * nbuf = (uint8_t*) malloc(server_cer_len); memcpy_P(nbuf, server_cer, server_cer_len); *buf = nbuf; DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); return server_cer_len; } if (strcmp(filename, "server.key") == 0) { uint8_t * nbuf = (uint8_t*) malloc(server_key_len); memcpy_P(nbuf, server_key, server_key_len); *buf = nbuf; DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); return server_key_len; } DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename); *buf = 0; return 0; #else 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; DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); return size; } file.close(); } DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename); *buf = 0; return 0; #endif } #endif void _onUpgrade(AsyncWebServerRequest *request) { webLog(request); if (!_authenticate(request)) return request->requestAuthentication(); char buffer[10]; if (!Update.hasError()) { sprintf_P(buffer, PSTR("OK")); } else { sprintf_P(buffer, PSTR("ERROR %d"), Update.getError()); } AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer); response->addHeader("Connection", "close"); if (!Update.hasError()) { _web_defer.once_ms(100, []() { customReset(CUSTOM_RESET_UPGRADE); ESP.restart(); }); } request->send(response); } void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { if (!index) { DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str()); Update.runAsync(true); if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { #ifdef DEBUG_PORT Update.printError(DEBUG_PORT); #endif } } if (!Update.hasError()) { if (Update.write(data, len) != len) { #ifdef DEBUG_PORT Update.printError(DEBUG_PORT); #endif } } if (final) { if (Update.end(true)){ DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len); } else { #ifdef DEBUG_PORT Update.printError(DEBUG_PORT); #endif } } else { DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len); } } // ----------------------------------------------------------------------------- bool _authenticate(AsyncWebServerRequest *request) { String password = getSetting("adminPass", ADMIN_PASS); char httpPassword[password.length() + 1]; password.toCharArray(httpPassword, password.length() + 1); return request->authenticate(WEB_USERNAME, httpPassword); } // ----------------------------------------------------------------------------- AsyncWebServer * webServer() { return _server; } void webLog(AsyncWebServerRequest *request) { DEBUG_MSG_P(PSTR("[WEBSERVER] Request: %s %s\n"), request->methodToString(), request->url().c_str()); } void webSetup() { // Cache the Last-Modifier header value snprintf_P(_last_modified, sizeof(_last_modified), PSTR("%s %s GMT"), __DATE__, __TIME__); // Create server #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED unsigned int port = 443; #else unsigned int port = getSetting("webPort", WEB_PORT).toInt(); #endif _server = new AsyncWebServer(port); // Rewrites _server->rewrite("/", "/index.html"); // Serve home (basic authentication protection) #if WEB_EMBEDDED _server->on("/index.html", HTTP_GET, _onHome); #endif _server->on("/config", HTTP_GET, _onGetConfig); _server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData); // Serve static files #if SPIFFS_SUPPORT _server->serveStatic("/", SPIFFS, "/") .setLastModified(_last_modified) .setFilter([](AsyncWebServerRequest *request) -> bool { webLog(request); return true; }); #endif // 404 _server->onNotFound([](AsyncWebServerRequest *request){ request->send(404); }); // Run server #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED _server->onSslFileRequest(_onCertificate, NULL); _server->beginSecure("server.cer", "server.key", NULL); #else _server->begin(); #endif DEBUG_MSG_P(PSTR("[WEBSERVER] Webserver running on port %d\n"), port); } #endif // WEB_SUPPORT