[PATCH 2/6] httpd: Match If-Modified-Since and Last-Modified to return 304

Sergey Ponomarev stokito at gmail.com
Sat Jan 9 21:09:47 UTC 2021


When client GET resource it may cache it locally and store Last-Modified header.
On next GET client sends the previous Last-Modified in the If-Modified-Since request header.
Server may check if the resource wasn't updated and return 304 Not modified without payload.

For example if we have last-mod.html then on first wget call it will be saved:

 $ wget -N http://localhost:8080/last-mod.html
 HTTP request sent, awaiting response... 200 OK
 ‘last-mod.html’ saved

But on next wget call with -N (for timestamp-checking) it won't be downloaded:

 $ wget -N http://localhost:8080/last-mod.html
 HTTP request sent, awaiting response... 304 Not Modified
 File ‘last-mod.html’ not modified on server. Omitting download.

To check last modification we can parse the If-Modified-Since as date and then compare with file's mtime.
Currently BB don't have a function to parse date.
But instead we can just generate Last-Modified and match it with If-Modified-Since as plain string.
In most cases client already have cached resource and it's not updated on server.
So the If-Modified-Since will be just the same as current Last-Modified.
If resource was updated on server then If-Modified-Since won't be equal to Last-Modified.
If client has cached resource then it's If-Modified-Since will never be before than Last-Modified.
There is one case when client doesn't have a cached resource but sends a If-Modified-Since with custom date.
This If-Modified-Since may be after a real Last-Modified e.g. If-Modified-Since 2 Jan but Last-Modified is 1 Jan.
Since we don't make an actual date comparison then server will send 200 while it might send 304.
But such cases will never occur with usual browser and while this case is supported by spec we can ignore it.

Signed-off-by: Sergey Ponomarev <stokito at gmail.com>
---
 networking/httpd.c | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/networking/httpd.c b/networking/httpd.c
index 5a3de2043..6cd40c13b 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -388,7 +388,7 @@ static const uint16_t http_response_type[] ALIGN2 = {
 	HTTP_PARTIAL_CONTENT,
 #endif
 	HTTP_MOVED_TEMPORARILY,
-#if ENABLE_FEATURE_HTTPD_ETAG
+#if ENABLE_FEATURE_HTTPD_ETAG || ENABLE_FEATURE_HTTPD_LAST_MODIFIED
 	HTTP_NOT_MODIFIED,
 #endif
 	HTTP_REQUEST_TIMEOUT,
@@ -421,7 +421,7 @@ static const struct {
 	{ "Partial Content", NULL },
 #endif
 	{ "Found", NULL },
-#if ENABLE_FEATURE_HTTPD_ETAG
+#if ENABLE_FEATURE_HTTPD_ETAG || ENABLE_FEATURE_HTTPD_LAST_MODIFIED
 	{ "Not Modified" },
 #endif
 	{ "Request Timeout", "No request appeared within 60 seconds" },
@@ -455,6 +455,9 @@ struct globals {
 	time_t last_mod;
 #if ENABLE_FEATURE_HTTPD_ETAG
 	char *if_none_match;
+#endif
+#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
+	char *if_modified_since;
 #endif
 	char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
 	const char *bind_addr_or_port;
@@ -1786,6 +1789,21 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
 		if (strstr(G.if_none_match, G.etag))
 			send_headers_and_exit(HTTP_NOT_MODIFIED);
 	}
+#endif
+#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
+	if (G.if_modified_since) {
+		if (DEBUG)
+			bb_perror_msg("If-Modified-Since and file's Last-Modified are: '%s' '%s'\n", G.if_modified_since, G.last_mod_date);
+		/* Simple match If-Modified-Since and Last-Modified as plain string.
+		 * In most cases client will send If-Modified-Since exactly the same as it retrieved on previous GET.
+		 * If a resource wasn't updated then they will be just the same and return 304.
+		 * If resource was updated then client's If-Modified-Since will be outdated and not the same and return 200.
+		 * If the If-Modified-Since is a custom date and not from cached resource then this match will always fail and return 200.
+		 * But such cases normally are not expected. */
+		if (strcmp(G.if_modified_since, G.last_mod_date) == 0) {
+			send_headers_and_exit(HTTP_NOT_MODIFIED);
+		}
+	}
 #endif
 	/* If you want to know about EPIPE below
 	 * (happens if you abort downloads from local httpd): */
@@ -2541,6 +2559,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
 			continue;
 		}
 #endif
+#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
+		if (STRNCASECMP(iobuf, "If-Modified-Since:") == 0) {
+			free(G.if_modified_since);
+			G.if_modified_since = xstrdup(skip_whitespace(iobuf + sizeof("If-Modified-Since:") - 1));
+			continue;
+		}
+#endif
 #if ENABLE_FEATURE_HTTPD_CGI
 		if (cgi_type != CGI_NONE) {
 			bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0);
-- 
2.27.0



More information about the busybox mailing list