diff options
author | Sergey Ponomarev <stokito@gmail.com> | 2020-08-15 23:52:48 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2020-08-15 23:54:48 +0200 |
commit | 4864a685967bd098307a0d64293b5de1fc5f8210 (patch) | |
tree | 6f2dd2c10582a302c13d1a08dc446a331a87287f | |
parent | b6efac31d864cffdf618744fd8a6fddbdee3eb4b (diff) | |
download | busybox-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.c | 57 |
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); |