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) | ||||
| async def to_code(config): | ||||
|     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) | ||||
|  | ||||
|     cg.add_define("USE_WEBSERVER") | ||||
|     version = config[CONF_VERSION] | ||||
|  | ||||
|     cg.add(paren.set_port(config[CONF_PORT])) | ||||
|     cg.add_define("USE_WEBSERVER") | ||||
|     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_js_url(config[CONF_JS_URL])) | ||||
|     cg.add(var.set_allow_ota(config[CONF_OTA])) | ||||
| @@ -103,13 +138,13 @@ async def to_code(config): | ||||
|     if CONF_CSS_INCLUDE in config: | ||||
|         cg.add_define("USE_WEBSERVER_CSS_INCLUDE") | ||||
|         path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) | ||||
|         with open(file=path, encoding="utf-8") as myfile: | ||||
|             cg.add(var.set_css_include(myfile.read())) | ||||
|         with open(file=path, encoding="utf-8") as css_file: | ||||
|             add_resource_as_progmem("CSS_INCLUDE", css_file.read()) | ||||
|     if CONF_JS_INCLUDE in config: | ||||
|         cg.add_define("USE_WEBSERVER_JS_INCLUDE") | ||||
|         path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) | ||||
|         with open(file=path, encoding="utf-8") as myfile: | ||||
|             cg.add(var.set_js_include(myfile.read())) | ||||
|         with open(file=path, encoding="utf-8") as js_file: | ||||
|             add_resource_as_progmem("JS_INCLUDE", js_file.read()) | ||||
|     cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) | ||||
|     if CONF_LOCAL in config and config[CONF_LOCAL]: | ||||
|         cg.add_define("USE_WEBSERVER_LOCAL") | ||||
|   | ||||
| @@ -90,10 +90,17 @@ WebServer::WebServer(web_server_base::WebServerBase *base) | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #if USE_WEBSERVER_VERSION == 1 | ||||
| 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; } | ||||
| #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; } | ||||
| #endif | ||||
|  | ||||
| void WebServer::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up web server..."); | ||||
| @@ -159,20 +166,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { | ||||
|   response->addHeader("Content-Encoding", "gzip"); | ||||
|   request->send(response); | ||||
| } | ||||
| #else | ||||
| #elif USE_WEBSERVER_VERSION == 1 | ||||
| void WebServer::handle_index_request(AsyncWebServerRequest *request) { | ||||
|   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(); | ||||
|   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>")); | ||||
|   stream->print(title.c_str()); | ||||
|   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 | ||||
|   stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">")); | ||||
| #endif | ||||
| @@ -182,7 +183,6 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { | ||||
|     stream->print(F("\">")); | ||||
|   } | ||||
|   stream->print(F("</head><body>")); | ||||
| #if USE_WEBSERVER_VERSION == 1 | ||||
|   stream->print(F("<article class=\"markdown-body\"><h1>")); | ||||
|   stream->print(title.c_str()); | ||||
|   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>")); | ||||
|   } | ||||
|   stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>")); | ||||
| #endif | ||||
| #ifdef USE_WEBSERVER_JS_INCLUDE | ||||
|   if (this->js_include_ != nullptr) { | ||||
|     stream->print(F("<script type=\"module\" src=\"/0.js\"></script>")); | ||||
|   } | ||||
| #endif | ||||
| #if USE_WEBSERVER_VERSION == 2 | ||||
|   stream->print(F("<esp-app></esp-app>")); | ||||
| #endif | ||||
|   if (strlen(this->js_url_) > 0) { | ||||
|     stream->print(F("<script src=\"")); | ||||
|     stream->print(this->js_url_); | ||||
|     stream->print(F("\"></script>")); | ||||
|   } | ||||
| #if USE_WEBSERVER_VERSION == 1 | ||||
|   stream->print(F("</article></body></html>")); | ||||
| #else | ||||
|   stream->print(F("</body></html>")); | ||||
| #endif | ||||
|  | ||||
|   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 | ||||
|  | ||||
| #ifdef USE_WEBSERVER_CSS_INCLUDE | ||||
| void WebServer::handle_css_request(AsyncWebServerRequest *request) { | ||||
|   AsyncResponseStream *stream = request->beginResponseStream("text/css"); | ||||
|   if (this->css_include_ != nullptr) { | ||||
|     stream->print(this->css_include_); | ||||
|   } | ||||
|  | ||||
|   request->send(stream); | ||||
|   AsyncWebServerResponse *response = | ||||
|       request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); | ||||
|   request->send(response); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_WEBSERVER_JS_INCLUDE | ||||
| void WebServer::handle_js_request(AsyncWebServerRequest *request) { | ||||
|   AsyncResponseStream *stream = request->beginResponseStream("text/javascript"); | ||||
|   if (this->js_include_ != nullptr) { | ||||
|     stream->addHeader("Access-Control-Allow-Origin", "*"); | ||||
|     stream->print(this->js_include_); | ||||
|   } | ||||
|  | ||||
|   request->send(stream); | ||||
|   AsyncWebServerResponse *response = | ||||
|       request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); | ||||
|   request->send(response); | ||||
| } | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,22 @@ | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/semphr.h> | ||||
| #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 web_server { | ||||
|  | ||||
| @@ -40,6 +56,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|  public: | ||||
|   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 | ||||
|    * 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); | ||||
|  | ||||
|   /** 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 | ||||
|    * https://esphome.io/_static/webserver-v1.min.js | ||||
|    * | ||||
|    * @param js_url The url to the web server script. | ||||
|    */ | ||||
|   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 | ||||
|    * | ||||
|    * @param js_include Local path to web server script. | ||||
|    */ | ||||
|   void set_js_include(const char *js_include); | ||||
| #endif | ||||
|  | ||||
|   /** Determine whether internal components should be displayed on the web server. | ||||
|    * Defaults to false. | ||||
| @@ -240,10 +262,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
|   web_server_base::WebServerBase *base_; | ||||
|   AsyncEventSource events_{"/events"}; | ||||
|   ListEntitiesIterator entities_iterator_; | ||||
| #if USE_WEBSERVER_VERSION == 1 | ||||
|   const char *css_url_{nullptr}; | ||||
|   const char *css_include_{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}; | ||||
| #endif | ||||
|   bool include_internal_{false}; | ||||
|   bool allow_ota_{true}; | ||||
| #ifdef USE_ESP32 | ||||
|   | ||||
| @@ -83,6 +83,7 @@ class WebServerBase : public Component { | ||||
|       return; | ||||
|     } | ||||
|     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", "*"); | ||||
|     this->server_->begin(); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user