aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Ponomarev <stokito@gmail.com>2020-08-15 23:52:48 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2020-08-15 23:54:48 +0200
commit4864a685967bd098307a0d64293b5de1fc5f8210 (patch)
tree6f2dd2c10582a302c13d1a08dc446a331a87287f
parentb6efac31d864cffdf618744fd8a6fddbdee3eb4b (diff)
downloadbusybox-w32-4864a685967bd098307a0d64293b5de1fc5f8210.tar.gz
busybox-w32-4864a685967bd098307a0d64293b5de1fc5f8210.tar.bz2
busybox-w32-4864a685967bd098307a0d64293b5de1fc5f8210.zip
httpd: Support caching via ETag header
If server responds with ETag then next time client can 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. function old new delta handle_incoming_and_exit 2135 2201 +66 http_response 88 96 +8 send_headers 676 683 +7 parse_conf 1362 1365 +3 http_response_type 22 24 +2 send_file_and_exit 847 841 -6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 5/1 up/down: 86/-6) Total: 80 bytes Signed-off-by: Sergey Ponomarev <stokito@gmail.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--networking/httpd.c57
1 files changed, 54 insertions, 3 deletions
diff --git a/networking/httpd.c b/networking/httpd.c
index 94f7297ad..0297cf9ec 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -215,6 +215,19 @@
215//config: Makes httpd send files using GZIP content encoding if the 215//config: Makes httpd send files using GZIP content encoding if the
216//config: client supports it and a pre-compressed <file>.gz exists. 216//config: client supports it and a pre-compressed <file>.gz exists.
217//config: 217//config:
218//config:config FEATURE_HTTPD_ETAG
219//config: bool "Support caching via ETag header"
220//config: default y
221//config: depends on HTTPD
222//config: help
223//config: If server responds with ETag then next time client (browser)
224//config: resend it via If-None-Match header.
225//config: Then httpd will check if file wasn't modified and if not,
226//config: return 304 Not Modified status code.
227//config: The ETag value is constructed from last modification date
228//config: in unix epoch, and size: "hex(last_mod)-hex(file_size)".
229//config: It's not completely reliable as hash functions but fair enough.
230//config:
218//config:config FEATURE_HTTPD_LAST_MODIFIED 231//config:config FEATURE_HTTPD_LAST_MODIFIED
219//config: bool "Add Last-Modified header to response" 232//config: bool "Add Last-Modified header to response"
220//config: default y 233//config: default y
@@ -328,6 +341,7 @@ enum {
328 HTTP_OK = 200, 341 HTTP_OK = 200,
329 HTTP_PARTIAL_CONTENT = 206, 342 HTTP_PARTIAL_CONTENT = 206,
330 HTTP_MOVED_TEMPORARILY = 302, 343 HTTP_MOVED_TEMPORARILY = 302,
344 HTTP_NOT_MODIFIED = 304,
331 HTTP_BAD_REQUEST = 400, /* malformed syntax */ 345 HTTP_BAD_REQUEST = 400, /* malformed syntax */
332 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ 346 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
333 HTTP_NOT_FOUND = 404, 347 HTTP_NOT_FOUND = 404,
@@ -345,7 +359,6 @@ enum {
345 HTTP_NO_CONTENT = 204, 359 HTTP_NO_CONTENT = 204,
346 HTTP_MULTIPLE_CHOICES = 300, 360 HTTP_MULTIPLE_CHOICES = 300,
347 HTTP_MOVED_PERMANENTLY = 301, 361 HTTP_MOVED_PERMANENTLY = 301,
348 HTTP_NOT_MODIFIED = 304,
349 HTTP_PAYMENT_REQUIRED = 402, 362 HTTP_PAYMENT_REQUIRED = 402,
350 HTTP_BAD_GATEWAY = 502, 363 HTTP_BAD_GATEWAY = 502,
351 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ 364 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
@@ -358,6 +371,9 @@ static const uint16_t http_response_type[] ALIGN2 = {
358 HTTP_PARTIAL_CONTENT, 371 HTTP_PARTIAL_CONTENT,
359#endif 372#endif
360 HTTP_MOVED_TEMPORARILY, 373 HTTP_MOVED_TEMPORARILY,
374#if ENABLE_FEATURE_HTTPD_ETAG
375 HTTP_NOT_MODIFIED,
376#endif
361 HTTP_REQUEST_TIMEOUT, 377 HTTP_REQUEST_TIMEOUT,
362 HTTP_NOT_IMPLEMENTED, 378 HTTP_NOT_IMPLEMENTED,
363#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 379#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -374,7 +390,6 @@ static const uint16_t http_response_type[] ALIGN2 = {
374 HTTP_NO_CONTENT, 390 HTTP_NO_CONTENT,
375 HTTP_MULTIPLE_CHOICES, 391 HTTP_MULTIPLE_CHOICES,
376 HTTP_MOVED_PERMANENTLY, 392 HTTP_MOVED_PERMANENTLY,
377 HTTP_NOT_MODIFIED,
378 HTTP_BAD_GATEWAY, 393 HTTP_BAD_GATEWAY,
379 HTTP_SERVICE_UNAVAILABLE, 394 HTTP_SERVICE_UNAVAILABLE,
380#endif 395#endif
@@ -389,6 +404,9 @@ static const struct {
389 { "Partial Content", NULL }, 404 { "Partial Content", NULL },
390#endif 405#endif
391 { "Found", NULL }, 406 { "Found", NULL },
407#if ENABLE_FEATURE_HTTPD_ETAG
408 { "Not Modified" },
409#endif
392 { "Request Timeout", "No request appeared within 60 seconds" }, 410 { "Request Timeout", "No request appeared within 60 seconds" },
393 { "Not Implemented", "The requested method is not recognized" }, 411 { "Not Implemented", "The requested method is not recognized" },
394#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 412#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -405,7 +423,6 @@ static const struct {
405 { "No Content" }, 423 { "No Content" },
406 { "Multiple Choices" }, 424 { "Multiple Choices" },
407 { "Moved Permanently" }, 425 { "Moved Permanently" },
408 { "Not Modified" },
409 { "Bad Gateway", "" }, 426 { "Bad Gateway", "" },
410 { "Service Unavailable", "" }, 427 { "Service Unavailable", "" },
411#endif 428#endif
@@ -419,6 +436,9 @@ struct globals {
419 smallint content_gzip; 436 smallint content_gzip;
420#endif 437#endif
421 time_t last_mod; 438 time_t last_mod;
439#if ENABLE_FEATURE_HTTPD_ETAG
440 char *if_none_match;
441#endif
422 char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */ 442 char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */
423 const char *bind_addr_or_port; 443 const char *bind_addr_or_port;
424 444
@@ -453,6 +473,9 @@ struct globals {
453#define sizeof_hdr_buf COMMON_BUFSIZE 473#define sizeof_hdr_buf COMMON_BUFSIZE
454 char *hdr_ptr; 474 char *hdr_ptr;
455 int hdr_cnt; 475 int hdr_cnt;
476#if ENABLE_FEATURE_HTTPD_ETAG
477 char etag[sizeof("'%llx-%llx'") + 2 * sizeof(long long)*3];
478#endif
456#if ENABLE_FEATURE_HTTPD_ERROR_PAGES 479#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
457 const char *http_error_page[ARRAY_SIZE(http_response_type)]; 480 const char *http_error_page[ARRAY_SIZE(http_response_type)];
458#endif 481#endif
@@ -1208,6 +1231,10 @@ static void send_headers(unsigned responseNum)
1208#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED 1231#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1209 "Last-Modified: %s\r\n" 1232 "Last-Modified: %s\r\n"
1210#endif 1233#endif
1234#if ENABLE_FEATURE_HTTPD_ETAG
1235 "ETag: %s\r\n"
1236#endif
1237
1211 /* Because of 4.4 (5), we can forgo sending of "Content-Length" 1238 /* Because of 4.4 (5), we can forgo sending of "Content-Length"
1212 * since we close connection afterwards, but it helps clients 1239 * since we close connection afterwards, but it helps clients
1213 * to e.g. estimate download times, show progress bars etc. 1240 * to e.g. estimate download times, show progress bars etc.
@@ -1218,6 +1245,9 @@ static void send_headers(unsigned responseNum)
1218#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED 1245#if ENABLE_FEATURE_HTTPD_LAST_MODIFIED
1219 date_str, 1246 date_str,
1220#endif 1247#endif
1248#if ENABLE_FEATURE_HTTPD_ETAG
1249 G.etag,
1250#endif
1221 file_size 1251 file_size
1222 ); 1252 );
1223 } 1253 }
@@ -1730,6 +1760,7 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
1730 } 1760 }
1731 } else { 1761 } else {
1732 fd = open(url, O_RDONLY); 1762 fd = open(url, O_RDONLY);
1763 /* file_size and last_mod are already populated */
1733 } 1764 }
1734 if (fd < 0) { 1765 if (fd < 0) {
1735 if (DEBUG) 1766 if (DEBUG)
@@ -1741,6 +1772,19 @@ static NOINLINE void send_file_and_exit(const char *url, int what)
1741 send_headers_and_exit(HTTP_NOT_FOUND); 1772 send_headers_and_exit(HTTP_NOT_FOUND);
1742 log_and_exit(); 1773 log_and_exit();
1743 } 1774 }
1775#if ENABLE_FEATURE_HTTPD_ETAG
1776 /* ETag is "hex(last_mod)-hex(file_size)" e.g. "5e132e20-417" */
1777 sprintf(G.etag, "\"%llx-%llx\"", (unsigned long long)last_mod, (unsigned long long)file_size);
1778
1779 if (G.if_none_match) {
1780 if (DEBUG)
1781 bb_perror_msg("If-None-Match and file's ETag are: '%s' '%s'\n", G.if_none_match, G.etag);
1782 /* Weak ETag comparision.
1783 * If-None-Match may have many ETags but they are quoted so we can use simple substring search */
1784 if (strstr(G.if_none_match, G.etag))
1785 send_headers_and_exit(HTTP_NOT_MODIFIED);
1786 }
1787#endif
1744 /* If you want to know about EPIPE below 1788 /* If you want to know about EPIPE below
1745 * (happens if you abort downloads from local httpd): */ 1789 * (happens if you abort downloads from local httpd): */
1746 signal(SIGPIPE, SIG_IGN); 1790 signal(SIGPIPE, SIG_IGN);
@@ -2480,6 +2524,13 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
2480 continue; 2524 continue;
2481 } 2525 }
2482#endif 2526#endif
2527#if ENABLE_FEATURE_HTTPD_ETAG
2528 if (STRNCASECMP(iobuf, "If-None-Match:") == 0) {
2529 free(G.if_none_match);
2530 G.if_none_match = xstrdup(skip_whitespace(iobuf + sizeof("If-None-Match:") - 1));
2531 continue;
2532 }
2533#endif
2483#if ENABLE_FEATURE_HTTPD_CGI 2534#if ENABLE_FEATURE_HTTPD_CGI
2484 if (cgi_type != CGI_NONE) { 2535 if (cgi_type != CGI_NONE) {
2485 bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0); 2536 bool ct = (STRNCASECMP(iobuf, "Content-Type:") == 0);