mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Construct web_server assets at build time instead of run time (#4944)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -81,6 +81,37 @@ CONFIG_SCHEMA = cv.All( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def build_index_html(config) -> str: | ||||||
|  |     html = "<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>" | ||||||
|  |     css_include = config.get(CONF_CSS_INCLUDE) | ||||||
|  |     js_include = config.get(CONF_JS_INCLUDE) | ||||||
|  |     if css_include: | ||||||
|  |         html += "<link rel=stylesheet href=/0.css>" | ||||||
|  |     if config[CONF_CSS_URL]: | ||||||
|  |         html += f'<link rel=stylesheet href="{config[CONF_CSS_URL]}">' | ||||||
|  |     html += "</head><body>" | ||||||
|  |     if js_include: | ||||||
|  |         html += "<script type=module src=/0.js></script>" | ||||||
|  |     html += "<esp-app></esp-app>" | ||||||
|  |     if config[CONF_JS_URL]: | ||||||
|  |         html += f'<script src="{config[CONF_JS_URL]}"></script>' | ||||||
|  |     html += "</body></html>" | ||||||
|  |     return html | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def add_resource_as_progmem(resource_name: str, content: str) -> None: | ||||||
|  |     """Add a resource to progmem.""" | ||||||
|  |     content_encoded = content.encode("utf-8") | ||||||
|  |     content_encoded_size = len(content_encoded) | ||||||
|  |     bytes_as_int = ", ".join(str(x) for x in content_encoded) | ||||||
|  |     uint8_t = f"const uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" | ||||||
|  |     size_t = ( | ||||||
|  |         f"const size_t ESPHOME_WEBSERVER_{resource_name}_SIZE = {content_encoded_size}" | ||||||
|  |     ) | ||||||
|  |     cg.add_global(cg.RawExpression(uint8_t)) | ||||||
|  |     cg.add_global(cg.RawExpression(size_t)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(40.0) | @coroutine_with_priority(40.0) | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) |     paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) | ||||||
| @@ -89,11 +120,15 @@ async def to_code(config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     cg.add_define("USE_WEBSERVER") |     cg.add_define("USE_WEBSERVER") | ||||||
|  |     version = config[CONF_VERSION] | ||||||
|  |  | ||||||
|     cg.add(paren.set_port(config[CONF_PORT])) |     cg.add(paren.set_port(config[CONF_PORT])) | ||||||
|     cg.add_define("USE_WEBSERVER") |     cg.add_define("USE_WEBSERVER") | ||||||
|     cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) |     cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) | ||||||
|     cg.add_define("USE_WEBSERVER_VERSION", config[CONF_VERSION]) |     cg.add_define("USE_WEBSERVER_VERSION", version) | ||||||
|  |     if version == 2: | ||||||
|  |         add_resource_as_progmem("INDEX_HTML", build_index_html(config)) | ||||||
|  |     else: | ||||||
|         cg.add(var.set_css_url(config[CONF_CSS_URL])) |         cg.add(var.set_css_url(config[CONF_CSS_URL])) | ||||||
|         cg.add(var.set_js_url(config[CONF_JS_URL])) |         cg.add(var.set_js_url(config[CONF_JS_URL])) | ||||||
|     cg.add(var.set_allow_ota(config[CONF_OTA])) |     cg.add(var.set_allow_ota(config[CONF_OTA])) | ||||||
| @@ -103,13 +138,13 @@ async def to_code(config): | |||||||
|     if CONF_CSS_INCLUDE in config: |     if CONF_CSS_INCLUDE in config: | ||||||
|         cg.add_define("USE_WEBSERVER_CSS_INCLUDE") |         cg.add_define("USE_WEBSERVER_CSS_INCLUDE") | ||||||
|         path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) |         path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) | ||||||
|         with open(file=path, encoding="utf-8") as myfile: |         with open(file=path, encoding="utf-8") as css_file: | ||||||
|             cg.add(var.set_css_include(myfile.read())) |             add_resource_as_progmem("CSS_INCLUDE", css_file.read()) | ||||||
|     if CONF_JS_INCLUDE in config: |     if CONF_JS_INCLUDE in config: | ||||||
|         cg.add_define("USE_WEBSERVER_JS_INCLUDE") |         cg.add_define("USE_WEBSERVER_JS_INCLUDE") | ||||||
|         path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) |         path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) | ||||||
|         with open(file=path, encoding="utf-8") as myfile: |         with open(file=path, encoding="utf-8") as js_file: | ||||||
|             cg.add(var.set_js_include(myfile.read())) |             add_resource_as_progmem("JS_INCLUDE", js_file.read()) | ||||||
|     cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) |     cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) | ||||||
|     if CONF_LOCAL in config and config[CONF_LOCAL]: |     if CONF_LOCAL in config and config[CONF_LOCAL]: | ||||||
|         cg.add_define("USE_WEBSERVER_LOCAL") |         cg.add_define("USE_WEBSERVER_LOCAL") | ||||||
|   | |||||||
| @@ -90,10 +90,17 @@ WebServer::WebServer(web_server_base::WebServerBase *base) | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #if USE_WEBSERVER_VERSION == 1 | ||||||
| void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; } | void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; } | ||||||
| void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } |  | ||||||
| void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } | void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
|  | void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
| void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } | void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void WebServer::setup() { | void WebServer::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up web server..."); |   ESP_LOGCONFIG(TAG, "Setting up web server..."); | ||||||
| @@ -159,20 +166,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { | |||||||
|   response->addHeader("Content-Encoding", "gzip"); |   response->addHeader("Content-Encoding", "gzip"); | ||||||
|   request->send(response); |   request->send(response); | ||||||
| } | } | ||||||
| #else | #elif USE_WEBSERVER_VERSION == 1 | ||||||
| void WebServer::handle_index_request(AsyncWebServerRequest *request) { | void WebServer::handle_index_request(AsyncWebServerRequest *request) { | ||||||
|   AsyncResponseStream *stream = request->beginResponseStream("text/html"); |   AsyncResponseStream *stream = request->beginResponseStream("text/html"); | ||||||
|   // All content is controlled and created by user - so allowing all origins is fine here. |  | ||||||
|   stream->addHeader("Access-Control-Allow-Origin", "*"); |  | ||||||
| #if USE_WEBSERVER_VERSION == 1 |  | ||||||
|   const std::string &title = App.get_name(); |   const std::string &title = App.get_name(); | ||||||
|   stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta " |   stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta " | ||||||
|                   "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>")); |                   "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>")); | ||||||
|   stream->print(title.c_str()); |   stream->print(title.c_str()); | ||||||
|   stream->print(F("</title>")); |   stream->print(F("</title>")); | ||||||
| #else |  | ||||||
|   stream->print(F("<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>")); |  | ||||||
| #endif |  | ||||||
| #ifdef USE_WEBSERVER_CSS_INCLUDE | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
|   stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">")); |   stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">")); | ||||||
| #endif | #endif | ||||||
| @@ -182,7 +183,6 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { | |||||||
|     stream->print(F("\">")); |     stream->print(F("\">")); | ||||||
|   } |   } | ||||||
|   stream->print(F("</head><body>")); |   stream->print(F("</head><body>")); | ||||||
| #if USE_WEBSERVER_VERSION == 1 |  | ||||||
|   stream->print(F("<article class=\"markdown-body\"><h1>")); |   stream->print(F("<article class=\"markdown-body\"><h1>")); | ||||||
|   stream->print(title.c_str()); |   stream->print(title.c_str()); | ||||||
|   stream->print(F("</h1>")); |   stream->print(F("</h1>")); | ||||||
| @@ -308,49 +308,40 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { | |||||||
|           "type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>")); |           "type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>")); | ||||||
|   } |   } | ||||||
|   stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>")); |   stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>")); | ||||||
| #endif |  | ||||||
| #ifdef USE_WEBSERVER_JS_INCLUDE | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
|   if (this->js_include_ != nullptr) { |   if (this->js_include_ != nullptr) { | ||||||
|     stream->print(F("<script type=\"module\" src=\"/0.js\"></script>")); |     stream->print(F("<script type=\"module\" src=\"/0.js\"></script>")); | ||||||
|   } |   } | ||||||
| #endif |  | ||||||
| #if USE_WEBSERVER_VERSION == 2 |  | ||||||
|   stream->print(F("<esp-app></esp-app>")); |  | ||||||
| #endif | #endif | ||||||
|   if (strlen(this->js_url_) > 0) { |   if (strlen(this->js_url_) > 0) { | ||||||
|     stream->print(F("<script src=\"")); |     stream->print(F("<script src=\"")); | ||||||
|     stream->print(this->js_url_); |     stream->print(this->js_url_); | ||||||
|     stream->print(F("\"></script>")); |     stream->print(F("\"></script>")); | ||||||
|   } |   } | ||||||
| #if USE_WEBSERVER_VERSION == 1 |  | ||||||
|   stream->print(F("</article></body></html>")); |   stream->print(F("</article></body></html>")); | ||||||
| #else |  | ||||||
|   stream->print(F("</body></html>")); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   request->send(stream); |   request->send(stream); | ||||||
| } | } | ||||||
|  | #elif USE_WEBSERVER_VERSION == 2 | ||||||
|  | void WebServer::handle_index_request(AsyncWebServerRequest *request) { | ||||||
|  |   AsyncWebServerResponse *response = | ||||||
|  |       request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE); | ||||||
|  |   request->send(response); | ||||||
|  | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_WEBSERVER_CSS_INCLUDE | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
| void WebServer::handle_css_request(AsyncWebServerRequest *request) { | void WebServer::handle_css_request(AsyncWebServerRequest *request) { | ||||||
|   AsyncResponseStream *stream = request->beginResponseStream("text/css"); |   AsyncWebServerResponse *response = | ||||||
|   if (this->css_include_ != nullptr) { |       request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); | ||||||
|     stream->print(this->css_include_); |   request->send(response); | ||||||
|   } |  | ||||||
|  |  | ||||||
|   request->send(stream); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_WEBSERVER_JS_INCLUDE | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
| void WebServer::handle_js_request(AsyncWebServerRequest *request) { | void WebServer::handle_js_request(AsyncWebServerRequest *request) { | ||||||
|   AsyncResponseStream *stream = request->beginResponseStream("text/javascript"); |   AsyncWebServerResponse *response = | ||||||
|   if (this->js_include_ != nullptr) { |       request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); | ||||||
|     stream->addHeader("Access-Control-Allow-Origin", "*"); |   request->send(response); | ||||||
|     stream->print(this->js_include_); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   request->send(stream); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,22 @@ | |||||||
| #include <freertos/FreeRTOS.h> | #include <freertos/FreeRTOS.h> | ||||||
| #include <freertos/semphr.h> | #include <freertos/semphr.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #if USE_WEBSERVER_VERSION == 2 | ||||||
|  | extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM; | ||||||
|  | extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
|  | extern const uint8_t ESPHOME_WEBSERVER_CSS_INCLUDE[] PROGMEM; | ||||||
|  | extern const size_t ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
|  | extern const uint8_t ESPHOME_WEBSERVER_JS_INCLUDE[] PROGMEM; | ||||||
|  | extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace web_server { | namespace web_server { | ||||||
|  |  | ||||||
| @@ -40,6 +56,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | |||||||
|  public: |  public: | ||||||
|   WebServer(web_server_base::WebServerBase *base); |   WebServer(web_server_base::WebServerBase *base); | ||||||
|  |  | ||||||
|  | #if USE_WEBSERVER_VERSION == 1 | ||||||
|   /** Set the URL to the CSS <link> that's sent to each client. Defaults to |   /** Set the URL to the CSS <link> that's sent to each client. Defaults to | ||||||
|    * https://esphome.io/_static/webserver-v1.min.css |    * https://esphome.io/_static/webserver-v1.min.css | ||||||
|    * |    * | ||||||
| @@ -47,24 +64,29 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | |||||||
|    */ |    */ | ||||||
|   void set_css_url(const char *css_url); |   void set_css_url(const char *css_url); | ||||||
|  |  | ||||||
|   /** Set local path to the script that's embedded in the index page. Defaults to |  | ||||||
|    * |  | ||||||
|    * @param css_include Local path to web server script. |  | ||||||
|    */ |  | ||||||
|   void set_css_include(const char *css_include); |  | ||||||
|  |  | ||||||
|   /** Set the URL to the script that's embedded in the index page. Defaults to |   /** Set the URL to the script that's embedded in the index page. Defaults to | ||||||
|    * https://esphome.io/_static/webserver-v1.min.js |    * https://esphome.io/_static/webserver-v1.min.js | ||||||
|    * |    * | ||||||
|    * @param js_url The url to the web server script. |    * @param js_url The url to the web server script. | ||||||
|    */ |    */ | ||||||
|   void set_js_url(const char *js_url); |   void set_js_url(const char *js_url); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
|  |   /** Set local path to the script that's embedded in the index page. Defaults to | ||||||
|  |    * | ||||||
|  |    * @param css_include Local path to web server script. | ||||||
|  |    */ | ||||||
|  |   void set_css_include(const char *css_include); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
|   /** Set local path to the script that's embedded in the index page. Defaults to |   /** Set local path to the script that's embedded in the index page. Defaults to | ||||||
|    * |    * | ||||||
|    * @param js_include Local path to web server script. |    * @param js_include Local path to web server script. | ||||||
|    */ |    */ | ||||||
|   void set_js_include(const char *js_include); |   void set_js_include(const char *js_include); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   /** Determine whether internal components should be displayed on the web server. |   /** Determine whether internal components should be displayed on the web server. | ||||||
|    * Defaults to false. |    * Defaults to false. | ||||||
| @@ -240,10 +262,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | |||||||
|   web_server_base::WebServerBase *base_; |   web_server_base::WebServerBase *base_; | ||||||
|   AsyncEventSource events_{"/events"}; |   AsyncEventSource events_{"/events"}; | ||||||
|   ListEntitiesIterator entities_iterator_; |   ListEntitiesIterator entities_iterator_; | ||||||
|  | #if USE_WEBSERVER_VERSION == 1 | ||||||
|   const char *css_url_{nullptr}; |   const char *css_url_{nullptr}; | ||||||
|   const char *css_include_{nullptr}; |  | ||||||
|   const char *js_url_{nullptr}; |   const char *js_url_{nullptr}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||||
|  |   const char *css_include_{nullptr}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_WEBSERVER_JS_INCLUDE | ||||||
|   const char *js_include_{nullptr}; |   const char *js_include_{nullptr}; | ||||||
|  | #endif | ||||||
|   bool include_internal_{false}; |   bool include_internal_{false}; | ||||||
|   bool allow_ota_{true}; |   bool allow_ota_{true}; | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   | |||||||
| @@ -83,6 +83,7 @@ class WebServerBase : public Component { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this->server_ = std::make_shared<AsyncWebServer>(this->port_); |     this->server_ = std::make_shared<AsyncWebServer>(this->port_); | ||||||
|  |     // All content is controlled and created by user - so allowing all origins is fine here. | ||||||
|     DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); |     DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); | ||||||
|     this->server_->begin(); |     this->server_->begin(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user