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 <stok...@gmail.com>
---
 networking/httpd.c | 73 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 70 insertions(+), 3 deletions(-)

diff --git a/networking/httpd.c b/networking/httpd.c
index 1cea33ddd..cc3f757aa 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -215,6 +215,17 @@
 //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_ETAG
+//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.
+//config:
 //config:config FEATURE_HTTPD_LAST_MODIFIED
 //config:      bool "Add Last-Modified header to response"
 //config:      default y
@@ -266,6 +277,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
@@ -328,6 +340,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,
@@ -345,7 +358,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 */
@@ -358,6 +370,9 @@ static const uint16_t http_response_type[] ALIGN2 = {
        HTTP_PARTIAL_CONTENT,
 #endif
        HTTP_MOVED_TEMPORARILY,
+#if ENABLE_FEATURE_HTTPD_ETAG
+       HTTP_NOT_MODIFIED,
+#endif
        HTTP_REQUEST_TIMEOUT,
        HTTP_NOT_IMPLEMENTED,
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -374,7 +389,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
@@ -389,6 +403,9 @@ static const struct {
        { "Partial Content", NULL },
 #endif
        { "Found", NULL },
+#if ENABLE_FEATURE_HTTPD_ETAG
+       { "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
@@ -405,7 +422,6 @@ static const struct {
        { "No Content" },
        { "Multiple Choices" },
        { "Moved Permanently" },
-       { "Not Modified" },
        { "Bad Gateway", "" },
        { "Service Unavailable", "" },
 #endif
@@ -419,6 +435,10 @@ struct globals {
        smallint content_gzip;
 #endif
        time_t last_mod;
+#if ENABLE_FEATURE_HTTPD_ETAG
+       char *if_none_match;
+       char *etag;
+#endif
        char *rmt_ip_str;       /* for $REMOTE_ADDR and $REMOTE_PORT */
        const char *bind_addr_or_port;
 
@@ -476,6 +496,10 @@ struct globals {
 #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_ETAG
+# define if_none_match    (G.if_none_match    )
+# define etag             (G.etag             )
+#endif
 #define ip_a_d            (G.ip_a_d           )
 #define g_realm           (G.g_realm          )
 #define remoteuser        (G.remoteuser       )
@@ -505,6 +529,8 @@ enum {
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
        IF_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
        IF_FEATURE_HTTPD_RANGES(range_start = -1;) \
+       IF_FEATURE_HTTPD_ETAG(if_none_match = NULL;) \
+       IF_FEATURE_HTTPD_ETAG(etag = NULL;) \
        bind_addr_or_port = "80"; \
        index_page = index_html; \
        file_size = -1; \
@@ -1047,6 +1073,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
@@ -1197,6 +1231,10 @@ static void send_headers(unsigned responseNum)
 #if ENABLE_FEATURE_HTTPD_RANGES
                        "Accept-Ranges: bytes\r\n"
 #endif
+#if ENABLE_FEATURE_HTTPD_ETAG
+                       "ETag: %s\r\n"
+#endif
+
        /* 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.
@@ -1204,9 +1242,17 @@ static void send_headers(unsigned responseNum)
         * but de-facto standard is to send it (see comment below).
         */
                        "Content-Length: %"OFF_FMT"u\r\n",
+#if ENABLE_FEATURE_HTTPD_ETAG
+                               etag,
+#endif
                                file_size
                );
 
+#if ENABLE_FEATURE_HTTPD_ETAG
+               free(etag);
+               etag = NULL;
+#endif
+
 #if ENABLE_FEATURE_HTTPD_DATE || ENABLE_FEATURE_HTTPD_LAST_MODIFIED
                strftime(date_str, sizeof(date_str), RFC1123FMT, 
gmtime_r(&last_mod, &tm));
                len += sprintf(iobuf + len, "Last-Modified: %s\r\n", date_str);
@@ -1721,6 +1767,7 @@ static NOINLINE void send_file_and_exit(const char *url, 
int what)
                }
        } else {
                fd = open(url, O_RDONLY);
+               /* file_size and last_mod are already populated */
        }
        if (fd < 0) {
                if (DEBUG)
@@ -1732,6 +1779,20 @@ 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_ETAG
+       etag = make_etag();
+       if (if_none_match) {
+               if (verbose)
+                       bb_perror_msg("If-None-Match and file's ETag are: '%s' 
'%s'\n", if_none_match, etag);
+               /* Weak ETag comparision.
+                * If-None-Match may have many ETags but they are quoted so we 
can use simple substring search */
+               if (strstr(if_none_match, etag)) {
+                       free(if_none_match);
+                       if_none_match = 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);
@@ -2471,6 +2532,12 @@ static void handle_incoming_and_exit(const 
len_and_sockaddr *fromAddr)
                        continue;
                }
 #endif
+#if ENABLE_FEATURE_HTTPD_ETAG
+               if (STRNCASECMP(iobuf, "If-None-Match:") == 0) {
+                       if_none_match = 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

Reply via email to