[prev in list] [next in list] [prev in thread] [next in thread] 

List:       busybox
Subject:    [PATCH v3 1/1] httpd: Support caching via 'ETag:' header
From:       Sergey Ponomarev <stokito () gmail ! com>
Date:       2020-07-12 20:58:31
Message-ID: 20200712205831.30295-1-stokito () gmail ! com
[Download RAW message or body]

If server respond with ETag then next time client (browser) resend it via \
If-None-Match header. Then httpd will check if file wasn't modified and if not return \
304 Not Modified status code. The ETag value is constructed from file's last \
modification date in unix epoch and it's size: "hex(last_mod)-hex(file_size)" e.g. \
"5e132e20-417" (with quotes). That means that it's not completely reliable as hash \
functions but fair enough. The same form of ETag is used by Nginx so load balancing \
of static content is safe.

Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
---
 networking/httpd.c | 79 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 72 insertions(+), 7 deletions(-)

diff --git a/networking/httpd.c b/networking/httpd.c
index 97b61fb77..c2f2d803b 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -214,6 +214,17 @@
 //config:	help
 //config:	Makes httpd send files using GZIP content encoding if the
 //config:	client supports it and a pre-compressed <file>.gz exists.
+//config:
+//config:config FEATURE_HTTPD_CACHE
+//config:	bool "Support caching via 'ETag' header"
+//config:	default y
+//config:	depends on HTTPD
+//config:	help
+//config:	If server respond with ETag then next time client (browser) resend it via \
If-None-Match header. +//config:	Then httpd will check if file wasn't modified and if \
not return 304 Not Modified status code. +//config:	The ETag value is constructed \
from file's last modification date in unix epoch and it's size: \
+//config:	"hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" (with quotes). \
+//config:	That means that it's not completely reliable as hash functions but fair \
enough.  
 //applet:IF_HTTPD(APPLET(httpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
 
@@ -248,6 +259,7 @@
 
 #include "libbb.h"
 #include "common_bufsiz.h"
+#include <inttypes.h>
 #if ENABLE_PAM
 /* PAM may include <locale.h>. We may need to undefine bbox's stub define: */
 # undef setlocale
@@ -310,6 +322,7 @@ enum {
 	HTTP_OK = 200,
 	HTTP_PARTIAL_CONTENT = 206,
 	HTTP_MOVED_TEMPORARILY = 302,
+	HTTP_NOT_MODIFIED = 304,
 	HTTP_BAD_REQUEST = 400,       /* malformed syntax */
 	HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
 	HTTP_NOT_FOUND = 404,
@@ -327,7 +340,6 @@ enum {
 	HTTP_NO_CONTENT = 204,
 	HTTP_MULTIPLE_CHOICES = 300,
 	HTTP_MOVED_PERMANENTLY = 301,
-	HTTP_NOT_MODIFIED = 304,
 	HTTP_PAYMENT_REQUIRED = 402,
 	HTTP_BAD_GATEWAY = 502,
 	HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
@@ -340,6 +352,9 @@ static const uint16_t http_response_type[] ALIGN2 = {
 	HTTP_PARTIAL_CONTENT,
 #endif
 	HTTP_MOVED_TEMPORARILY,
+#if ENABLE_FEATURE_HTTPD_CACHE
+	HTTP_NOT_MODIFIED,
+#endif
 	HTTP_REQUEST_TIMEOUT,
 	HTTP_NOT_IMPLEMENTED,
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -356,7 +371,6 @@ static const uint16_t http_response_type[] ALIGN2 = {
 	HTTP_NO_CONTENT,
 	HTTP_MULTIPLE_CHOICES,
 	HTTP_MOVED_PERMANENTLY,
-	HTTP_NOT_MODIFIED,
 	HTTP_BAD_GATEWAY,
 	HTTP_SERVICE_UNAVAILABLE,
 #endif
@@ -371,6 +385,9 @@ static const struct {
 	{ "Partial Content", NULL },
 #endif
 	{ "Found", NULL },
+#if ENABLE_FEATURE_HTTPD_CACHE
+	{ "Not Modified" },
+#endif
 	{ "Request Timeout", "No request appeared within 60 seconds" },
 	{ "Not Implemented", "The requested method is not recognized" },
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -387,7 +404,6 @@ static const struct {
 	{ "No Content" },
 	{ "Multiple Choices" },
 	{ "Moved Permanently" },
-	{ "Not Modified" },
 	{ "Bad Gateway", "" },
 	{ "Service Unavailable", "" },
 #endif
@@ -400,7 +416,11 @@ struct globals {
 	/* client can handle gzip / we are going to send gzip */
 	smallint content_gzip;
 #endif
+#if ENABLE_FEATURE_HTTPD_CACHE
+	char *req_etag;
+	char *real_etag;
 	time_t last_mod;
+#endif
 	char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
 	const char *bind_addr_or_port;
 
@@ -457,7 +477,11 @@ struct globals {
 #define index_page        (G.index_page       )
 #define found_mime_type   (G.found_mime_type  )
 #define found_moved_temporarily (G.found_moved_temporarily)
-#define last_mod          (G.last_mod         )
+#if ENABLE_FEATURE_HTTPD_CACHE
+# define req_etag         (G.req_etag         )
+# define real_etag        (G.real_etag        )
+# define last_mod          (G.last_mod         )
+#endif
 #define ip_a_d            (G.ip_a_d           )
 #define g_realm           (G.g_realm          )
 #define remoteuser        (G.remoteuser       )
@@ -486,6 +510,8 @@ enum {
 	setup_common_bufsiz(); \
 	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 	IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
+	IF_FEATURE_HTTPD_CACHE(req_etag = NULL;) \
+	IF_FEATURE_HTTPD_CACHE(real_etag = NULL;) \
 	IF_FEATURE_HTTPD_RANGES(range_start = -1;) \
 	bind_addr_or_port = "80"; \
 	index_page = index_html; \
@@ -1029,6 +1055,14 @@ static void log_and_exit(void)
 	_exit(xfunc_error_retval);
 }
 
+/*
+ * ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417"
+ */
+static char *make_etag(void)
+{
+	return xasprintf("\"%" PRIx64 "-%" PRIx64 "\"", last_mod, file_size);
+}
+
 /*
  * Create and send HTTP response headers.
  * The arguments are combined and sent as one write operation.  Note that
@@ -1166,8 +1200,6 @@ static void send_headers(unsigned responseNum)
 #if ENABLE_FEATURE_HTTPD_RANGES
 			"Accept-Ranges: bytes\r\n"
 #endif
-			/* Instead of Last-Modified formatted date send it as a plain unix timestamp via \
                ETag */
-			"ETag: %ld\r\n"
 	/* Because of 4.4 (5), we can forgo sending of "Content-Length"
 	 * since we close connection afterwards, but it helps clients
 	 * to e.g. estimate download times, show progress bars etc.
@@ -1175,9 +1207,16 @@ static void send_headers(unsigned responseNum)
 	 * but de-facto standard is to send it (see comment below).
 	 */
 			"Content-Length: %"OFF_FMT"u\r\n",
-				last_mod,
 				file_size
 		);
+
+#if ENABLE_FEATURE_HTTPD_CACHE
+		if (real_etag) {
+			len += sprintf(iobuf + len, "ETag: %s\r\n", real_etag);
+			free(real_etag);
+			real_etag = NULL;
+		}
+#endif
 	}
 
 	/* This should be "Transfer-Encoding", not "Content-Encoding":
@@ -1681,13 +1720,16 @@ static NOINLINE void send_file_and_exit(const char *url, int \
what)  struct stat sb;
 			fstat(fd, &sb);
 			file_size = sb.st_size;
+#if ENABLE_FEATURE_HTTPD_CACHE
 			last_mod = sb.st_mtime;
+#endif
 		} else {
 			IF_FEATURE_HTTPD_GZIP(content_gzip = 0;)
 			fd = open(url, O_RDONLY);
 		}
 	} else {
 		fd = open(url, O_RDONLY);
+		/* file_size and last_mod are already populated */
 	}
 	if (fd < 0) {
 		if (DEBUG)
@@ -1699,6 +1741,21 @@ static NOINLINE void send_file_and_exit(const char *url, int \
what)  send_headers_and_exit(HTTP_NOT_FOUND);
 		log_and_exit();
 	}
+#if ENABLE_FEATURE_HTTPD_CACHE
+	real_etag = make_etag();
+	if (verbose)
+		bb_perror_msg("req_etag and real_etag: '%s' '%s'\n", req_etag, real_etag);
+	if (req_etag) {
+		if (!strcmp(req_etag, real_etag)) {
+			// Already 304 so ETag not needed and can be freed
+			free(real_etag);
+			real_etag = NULL;
+			free(req_etag);
+			req_etag = NULL;
+			send_headers_and_exit(HTTP_NOT_MODIFIED);
+		}
+	}
+#endif
 	/* If you want to know about EPIPE below
 	 * (happens if you abort downloads from local httpd): */
 	signal(SIGPIPE, SIG_IGN);
@@ -2334,7 +2391,9 @@ static void handle_incoming_and_exit(const len_and_sockaddr \
*fromAddr)  #endif
 		if (!found_moved_temporarily) {
 			file_size = sb.st_size;
+#if ENABLE_FEATURE_HTTPD_CACHE
 			last_mod = sb.st_mtime;
+#endif
 		}
 	}
 #if ENABLE_FEATURE_HTTPD_CGI
@@ -2438,6 +2497,12 @@ static void handle_incoming_and_exit(const len_and_sockaddr \
*fromAddr)  continue;
 		}
 #endif
+#if ENABLE_FEATURE_HTTPD_CACHE
+		if (STRNCASECMP(iobuf, "If-None-Match:") == 0) {
+			req_etag = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1));
+			continue;
+		}
+#endif
 #if ENABLE_FEATURE_HTTPD_CGI
 		if (cgi_type != CGI_NONE) {
 			bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0);
-- 
2.25.1

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic