[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