Forráskód Böngészése

httpd: try to make the client cache the contents

Try to make the client cache the content, and look for If-None-Match
headers with our current ETag.
H. Peter Anvin 2 éve
szülő
commit
913451e978
4 módosított fájl, 149 hozzáadás és 31 törlés
  1. 149 31
      esp32/max80/httpd.c
  2. BIN
      esp32/output/max80.ino.bin
  3. BIN
      fpga/output/v1.fw
  4. BIN
      fpga/output/v2.fw

+ 149 - 31
esp32/max80/httpd.c

@@ -7,8 +7,96 @@
 #include <incbin.h>
 #include <unzipLIB.h>
 
+/*
+ * Allow the client to cache static content for this many seconds;
+ * this improves responsiveness signficantly.
+ */
+#define HTTPD_STATIC_CACHE_AGE	300 /* 5 min */
+
 static httpd_handle_t httpd;
 
+#define TIMEBUF_LEN 32
+static const char *http_date(time_t when, char *timebuf)
+{
+    strftime(timebuf, TIMEBUF_LEN,
+	     "%a, %d %b %Y %H:%M:%S GMT", gmtime(&when));
+
+    return timebuf;
+}
+
+static const char *http_now(void)
+{
+    static char timebuf[TIMEBUF_LEN];
+    return http_date(time(NULL), timebuf);
+}
+
+static const char *http_dos_date(uint32_t dos_date)
+{
+    static char timebuf[TIMEBUF_LEN];
+    struct tm tm;
+    time_t t;
+
+    tm.tm_sec   = (dos_date << 1) & 63;
+    tm.tm_min   = (dos_date >> 5) & 63;
+    tm.tm_hour  = (dos_date >> 11) & 31;
+    tm.tm_mday  = (dos_date >> 16) & 31;
+    tm.tm_mon   = ((dos_date >> 21) & 15) - 1;
+    tm.tm_year  = (dos_date >> 25) + 80;
+    tm.tm_isdst = -1;
+
+    return http_date(mktime(&tm), timebuf);
+}
+
+static esp_err_t httpd_error(httpd_req_t *req,
+			     unsigned int errcode, const char *msg)
+{
+    char *header = NULL;
+    char *body = NULL;
+    int hlen, blen;
+    int rv = ESP_ERR_NO_MEM;
+
+    blen = asprintf(&body,
+		    "<!DOCTYPE html>\r\n"
+		    "<html>\r\n"
+		    "<head>\r\n"
+		    "<title>Error %u: %s</title>\r\n"
+		    "</head>\r\n"
+		    "<body>\r\n"
+		    "<h1>Error %u</h1>\r\n"
+		    "<p>%s</p>\r\n"
+		    "</body>\r\n"
+		    "</html>\r\n",
+		    errcode, msg, errcode, msg);
+
+    if (!body)
+	goto out;
+
+    hlen = asprintf(&header,
+		    "HTTP/1.1 %u %s\r\n"
+		    "Content-Type: text/html; charset=\"UTF-8\"\r\n"
+		    "Content-Length: %d\r\n"
+		    "Date: %s\r\n"
+		    "Cache-Control: no-cache\r\n"
+		    "Connection: close\r\n"
+		    "\r\n",
+		    errcode, msg, blen, http_now());
+
+    if (!header)
+	goto out;
+
+    rv = httpd_send(req, header, hlen);
+    if (!rv)
+	rv = httpd_send(req, body, blen);
+
+out:
+    if (header)
+	free(header);
+    if (body)
+	free(body);
+
+    return rv;
+}
+
 esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 {
     char *response;
@@ -19,15 +107,15 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 	   req->content_len, req->uri);
 
     if (!req->content_len) {
-	return httpd_resp_send_err(req, 411, "Length required");
+	return httpd_error(req, 411, "Length required");
     }
 
     rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
 
     if (rv == FWUPDATE_ERR_IN_PROGRESS)
-	return httpd_resp_send_err(req, 409, "Firmware update already in progress");
+	return httpd_error(req, 409, "Firmware update already in progress");
     else if (rv)
-	return httpd_resp_send_err(req, 500, "Firmware update failed");
+	return httpd_error(req, 500, "Firmware update failed");
 
     len = asprintf(&response,
 		   "<!DOCTYPE html>\r\n"
@@ -43,6 +131,8 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 		   reboot_delayed());
 
     /* 200 and text/html are the response defaults, no need to set */
+    httpd_resp_set_hdr(req, "Date", http_now());
+    httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
     httpd_resp_set_hdr(req, "Connection", "close");
     err = httpd_resp_send(req, response, len);
     free(response);
@@ -112,12 +202,12 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     buffer = malloc(buffer_size);
     zip = malloc(sizeof *zip);
     if (!buffer || !zip) {
-	httpd_resp_send_err(req, 503, "Out of memory");
+	err = httpd_error(req, 503, "Out of memory");
 	goto out;
     }
 
     if (enduri - uri + 1 + sizeof index_filename >= buffer_size) {
-	err = httpd_resp_send_err(req, 414, "URI too long");
+	err = httpd_error(req, 414, "URI too long");
 	goto out;
     }
 
@@ -128,7 +218,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 		  zip, NULL, NULL, NULL, NULL);
     if (!unz) {
 	MSG("[HTTP] unzOpen failed!\n");
-	err = httpd_resp_send_err(req, 500, "Cannot open content archive");
+	err = httpd_error(req, 500, "Cannot open content archive");
 	goto out;
     }
 
@@ -136,7 +226,8 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	if (add_index) {
 	    if (p > buffer)
 		*p++ = '/';
-	    p = mempcpy(p, index_filename, sizeof index_filename);
+	    memcpy(p, index_filename, sizeof index_filename);
+	    p += sizeof index_filename - 1; /* Point to final NUL */
 	}
 
 	MSG("trying to open: %s\n", buffer);
@@ -144,7 +235,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	    break;
 
 	if (add_index) {
-	    err = httpd_resp_send_404(req);
+	    err = httpd_error(req, 404, "File not found");
 	    goto out;
 	}
 
@@ -170,37 +261,64 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
     unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
 
     /*
-     * This is kind of brain dead, but it seems like the only sane
-     * way to not have to build the whole response in memory even
-     * though the length is known a priori.
+     * Hopefully the combination of date and CRC
+     * is strong enough to quality for a "strong" ETag
      */
+    char etag[16+3+1];
+    snprintf(etag, sizeof etag, "\"%08x:%08x\"",
+	     fileinfo.dosDate, fileinfo.crc);
+
+    bool skip_body = req->method == HTTP_HEAD || !fileinfo.uncompressed_size;
+    bool skip_meta = false;
+    const char *response = "200 OK";
+
+    if (req->method == HTTP_GET &&
+	httpd_req_get_hdr_value_str(req, "If-None-Match",
+				    buffer, buffer_size) == ESP_OK &&
+	strstr(buffer, etag)) {
+	skip_body = skip_meta = true;
+	response = "304 Not Modified";
+    }
+
+    len = snprintf(buffer, buffer_size-2,
+		   "HTTP/1.1 %s\r\n"
+		   "Date: %s\r\n"
+		   "ETag: %s\r\n"
+		   "Cache-Control: max-age=%d\r\n",
+		   response,
+		   http_now(),
+		   etag,
+		   HTTPD_STATIC_CACHE_AGE);
+
+    if (len < buffer_size-2 && !skip_meta) {
+	const char *mime_extra =
+	    mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "";
+
+	len += snprintf(buffer + len, buffer_size-2 - len,
+			"Content-Type: %s%s\r\n"
+			"Content-Length: %u\r\n"
+			"Allow: GET, HEAD\r\n"
+			"Last-Modified: %s\r\n"
+			"%s",
+			mime_type->mime, mime_extra,
+			fileinfo.uncompressed_size,
+			http_dos_date(fileinfo.dosDate),
+			skip_body ? "" : "Connection: close\r\n");
+    }
 
-    len = snprintf(buffer, buffer_size,
-		   "HTTP/1.1 200 OK\r\n"
-		   "Content-Type: %s%s\r\n"
-		   "Content-Length: %u\r\n"
-		   "Allow: GET, HEAD\r\n"
-		   "Etag: \"%08x:%08x\"\r\n"
-		   "Connection: close\r\n"
-		   "\r\n",
-		   mime_type->mime,
-		   mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "",
-		   fileinfo.uncompressed_size,
-		   /*
-		    * Hopefully the combination of date and CRC
-		    * is strong enough to quality for a "strong" ETag
-		    */
-		   fileinfo.dosDate, fileinfo.crc);
-
-    if (len >= buffer_size) {
+    if (len >= buffer_size-2) {
 	err = httpd_resp_send_err(req, 500, "buffer_size too small");
 	goto out;
     }
 
+    buffer[len++] = '\r';
+    buffer[len++] = '\n';
+
     err = httpd_send(req, buffer, len);
-    if (err != len || req->method == HTTP_HEAD ||
-	!fileinfo.uncompressed_size)
+    if (skip_body || err != len) {
+	/* No need to spend time uncompressing the file content */
 	goto out;
+    }
 
     if (unzOpenCurrentFile(unz) != UNZ_OK) {
 	err = httpd_resp_send_err(req, 500, "Cannot open file in archive");

BIN
esp32/output/max80.ino.bin


BIN
fpga/output/v1.fw


BIN
fpga/output/v2.fw