#! /bin/sh /usr/share/dpatch/dpatch-run ## ngx_http_etag-0.1.dpatch by Kirill A. Kroinskiy ## ## All lines beginning with `## DP:' are a description of the patch. ## DP: Implement support ETag and If-Match header @DPATCH@ diff --git a/src/http/modules/ngx_http_empty_gif_module.c b/src/http/modules/ngx_http_empty_gif_module.c index 8450cae..cbe64a8 100644 --- a/src/http/modules/ngx_http_empty_gif_module.c +++ b/src/http/modules/ngx_http_empty_gif_module.c @@ -130,6 +130,11 @@ ngx_http_empty_gif_handler(ngx_http_request_t *r) r->headers_out.content_length_n = sizeof(ngx_empty_gif); r->headers_out.last_modified_time = 23349600; + r->headers_out.etag_size = 40; + r->headers_out.etag_time = 5; + r->headers_out.etag_uniq = 6535; + + return ngx_http_send_header(r); } @@ -150,6 +155,10 @@ ngx_http_empty_gif_handler(ngx_http_request_t *r) r->headers_out.content_length_n = sizeof(ngx_empty_gif); r->headers_out.last_modified_time = 23349600; + r->headers_out.etag_size = 40; + r->headers_out.etag_time = 5; + r->headers_out.etag_uniq = 6535; + rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { diff --git a/src/http/modules/ngx_http_flv_module.c b/src/http/modules/ngx_http_flv_module.c index 3cc7d81..8e72e9e 100644 --- a/src/http/modules/ngx_http_flv_module.c +++ b/src/http/modules/ngx_http_flv_module.c @@ -197,6 +197,10 @@ ngx_http_flv_handler(ngx_http_request_t *r) r->headers_out.content_length_n = len; r->headers_out.last_modified_time = of.mtime; + r->headers_out.etag_size = of.size; + r->headers_out.etag_time = of.mtime; + r->headers_out.etag_uniq = of.uniq; + if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_gzip_static_module.c b/src/http/modules/ngx_http_gzip_static_module.c index affb766..cf89b36 100644 --- a/src/http/modules/ngx_http_gzip_static_module.c +++ b/src/http/modules/ngx_http_gzip_static_module.c @@ -190,6 +190,10 @@ ngx_http_gzip_static_handler(ngx_http_request_t *r) r->headers_out.content_length_n = of.size; r->headers_out.last_modified_time = of.mtime; + r->headers_out.etag_size = of.size; + r->headers_out.etag_time = of.mtime; + r->headers_out.etag_uniq = of.uniq; + if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_not_modified_filter_module.c b/src/http/modules/ngx_http_not_modified_filter_module.c index 389a3a9..fd57128 100644 --- a/src/http/modules/ngx_http_not_modified_filter_module.c +++ b/src/http/modules/ngx_http_not_modified_filter_module.c @@ -50,17 +50,44 @@ static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r) { + u_char *p, *etag; time_t ims; ngx_http_core_loc_conf_t *clcf; if (r->headers_out.status != NGX_HTTP_OK || r != r->main - || r->headers_in.if_modified_since == NULL - || r->headers_out.last_modified_time == -1) + || ((r->headers_in.if_modified_since == NULL + || r->headers_out.last_modified_time == -1) + && (r->headers_in.if_match == NULL + || r->headers_out.etag_size == -1 + || r->headers_out.etag_time == -1 + || r->headers_out.etag_uniq == (ngx_file_uniq_t) -1))) { return ngx_http_next_header_filter(r); } + if (r->headers_in.if_match) { + etag = ngx_palloc(r->pool, NGX_OFF_T_LEN + sizeof("_") - 1 + + NGX_TIME_T_LEN + sizeof("_") - 1 + NGX_INT_T_LEN); + + if (etag == NULL) { + return NGX_ERROR; + } + + p = ngx_sprintf(etag, "%XO-%XM-%Xd", + r->headers_out.etag_size, + r->headers_out.etag_time, + r->headers_out.etag_uniq); + + if (ngx_strncmp(r->headers_in.if_match->value.data, etag, + (ngx_uint_t)(etag - p) > r->headers_in.if_match->value.len ? + (ngx_uint_t)(etag - p) : r->headers_in.if_match->value.len)) { + return ngx_http_next_header_filter(r); + } + + goto not_modified; + } + ims = ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len); @@ -78,6 +105,8 @@ ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r) } } + not_modified: + r->headers_out.status = NGX_HTTP_NOT_MODIFIED; r->headers_out.content_type.len = 0; ngx_http_clear_content_length(r); diff --git a/src/http/modules/ngx_http_range_filter_module.c b/src/http/modules/ngx_http_range_filter_module.c index 4a15637..711f1eb 100644 --- a/src/http/modules/ngx_http_range_filter_module.c +++ b/src/http/modules/ngx_http_range_filter_module.c @@ -146,6 +146,7 @@ static ngx_int_t ngx_http_range_header_filter(ngx_http_request_t *r) { time_t if_range; + u_char *p, *etag; ngx_int_t rc; ngx_http_range_filter_ctx_t *ctx; @@ -172,13 +173,39 @@ ngx_http_range_header_filter(ngx_http_request_t *r) if_range = ngx_http_parse_time(r->headers_in.if_range->value.data, r->headers_in.if_range->value.len); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http ir:%d lm:%d", - if_range, r->headers_out.last_modified_time); + if (if_range != NGX_ERROR) { + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http ir:%d lm:%d", + if_range, r->headers_out.last_modified_time); + + if (if_range != r->headers_out.last_modified_time) { + goto next_filter; + } + } + } - if (if_range != r->headers_out.last_modified_time) { + if (r->headers_in.if_range && r->headers_out.etag_size != -1 + && r->headers_out.etag_time != -1 + && r->headers_out.etag_uniq != (ngx_file_uniq_t) -1) { + etag = ngx_palloc(r->pool, NGX_OFF_T_LEN + sizeof("_") - 1 + + NGX_TIME_T_LEN + sizeof("_") - 1 + NGX_INT_T_LEN); + + if (etag == NULL) { + return NGX_ERROR; + } + + p = ngx_sprintf(etag, "%XO-%XM-%Xd", + r->headers_out.etag_size, + r->headers_out.etag_time, + r->headers_out.etag_uniq); + + if (ngx_strncmp(r->headers_in.if_match->value.data, etag, + (ngx_uint_t)(etag - p) > r->headers_in.if_match->value.len ? + (ngx_uint_t)(etag - p) : r->headers_in.if_match->value.len)) { goto next_filter; } + } ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t)); diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index 04ab042..40f3931 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -360,6 +360,7 @@ ngx_http_ssi_header_filter(ngx_http_request_t *r) if (r == r->main) { ngx_http_clear_content_length(r); ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); } return ngx_http_next_header_filter(r); diff --git a/src/http/modules/ngx_http_static_module.c b/src/http/modules/ngx_http_static_module.c index 9ff1f81..1131bd4 100644 --- a/src/http/modules/ngx_http_static_module.c +++ b/src/http/modules/ngx_http_static_module.c @@ -213,6 +213,10 @@ ngx_http_static_handler(ngx_http_request_t *r) r->headers_out.content_length_n = of.size; r->headers_out.last_modified_time = of.mtime; + r->headers_out.etag_size = of.size; + r->headers_out.etag_time = of.mtime; + r->headers_out.etag_uniq = of.uniq; + if (ngx_http_set_content_type(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_sub_filter_module.c b/src/http/modules/ngx_http_sub_filter_module.c index f40a3c6..2cdccde 100644 --- a/src/http/modules/ngx_http_sub_filter_module.c +++ b/src/http/modules/ngx_http_sub_filter_module.c @@ -161,6 +161,7 @@ ngx_http_sub_header_filter(ngx_http_request_t *r) if (r == r->main) { ngx_http_clear_content_length(r); ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); } return ngx_http_next_header_filter(r); diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c index f65ab42..d8721a4 100644 --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -346,6 +346,7 @@ ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx, } ngx_http_clear_last_modified(r); + ngx_http_clear_etag_modified(r); } rc = ngx_http_next_header_filter(r); diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index b4bd40a..602b472 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -1878,6 +1878,7 @@ ngx_http_subrequest(ngx_http_request_t *r, ngx_http_clear_content_length(sr); ngx_http_clear_accept_ranges(sr); ngx_http_clear_last_modified(sr); + ngx_http_clear_etag(sr); sr->request_body = r->request_body; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 032d07a..801efaa 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -451,5 +451,15 @@ extern ngx_uint_t ngx_http_max_module; r->headers_out.last_modified = NULL; \ } +#define ngx_http_clear_etag(r) \ + \ + r->headers_out.etag_size = -1; \ + r->headers_out.etag_time = -1; \ + r->headers_out.etag_uniq = (ngx_file_uniq_t) -1; \ + if (r->headers_out.etag) { \ + r->headers_out.etag->hash = 0; \ + r->headers_out.etag = NULL; \ + } + #endif /* _NGX_HTTP_CORE_H_INCLUDED_ */ diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index de95333..b7b1c61 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -188,6 +188,20 @@ ngx_http_header_filter(ngx_http_request_t *r) } } + if (r->headers_out.etag_size != -1 || + r->headers_out.etag_time != -1 || + r->headers_out.etag_uniq != (ngx_file_uniq_t) -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.etag_size = -1; + r->headers_out.etag_time = -1; + r->headers_out.etag_uniq = (ngx_file_uniq_t) -1; + r->headers_out.etag = NULL; + } + } + len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1 /* the end of the header */ + sizeof(CRLF) - 1; @@ -214,6 +228,10 @@ ngx_http_header_filter(ngx_http_request_t *r) r->headers_out.last_modified = NULL; r->headers_out.content_length = NULL; r->headers_out.content_length_n = -1; + r->headers_out.etag_size = 1; + r->headers_out.etag_time = 1; + r->headers_out.etag_uniq = 1; + r->headers_out.etag = NULL; } } else if (r->headers_out.status < NGX_HTTP_BAD_REQUEST) { @@ -276,6 +294,15 @@ ngx_http_header_filter(ngx_http_request_t *r) len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1; } + if (r->headers_out.etag == NULL + && r->headers_out.etag_size != -1 + && r->headers_out.etag_time != -1 + && r->headers_out.etag_uniq != (ngx_file_uniq_t) -1) + { + len += sizeof("ETag: ") - 1 + NGX_OFF_T_LEN + sizeof("_") - 1 + + NGX_TIME_T_LEN + sizeof("_") - 1 + NGX_INT_T_LEN + sizeof(CRLF) - 1; + } + if (r->headers_out.location && r->headers_out.location->value.len && r->headers_out.location->value.data[0] == '/') @@ -457,6 +484,17 @@ ngx_http_header_filter(ngx_http_request_t *r) *b->last++ = CR; *b->last++ = LF; } + if (r->headers_out.etag == NULL + && r->headers_out.etag_size != -1 + && r->headers_out.etag_time != -1 + && r->headers_out.etag_uniq != (ngx_file_uniq_t) -1) + { + b->last = ngx_sprintf(b->last, "ETag: %XO-%XM-%Xd" CRLF, + r->headers_out.etag_size, + r->headers_out.etag_time, + r->headers_out.etag_uniq); + } + if (host.data) { p = b->last + sizeof("Location: ") - 1; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ab4c0e0..43021dc 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -83,6 +83,10 @@ ngx_http_header_t ngx_http_headers_in[] = { offsetof(ngx_http_headers_in_t, if_modified_since), ngx_http_process_unique_header_line }, + { ngx_string("If-Match"), + offsetof(ngx_http_headers_in_t, if_match), + ngx_http_process_unique_header_line }, + { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent), ngx_http_process_user_agent }, @@ -450,6 +454,9 @@ ngx_http_init_request(ngx_event_t *rev) r->headers_in.keep_alive_n = -1; r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; + r->headers_out.etag_size = -1; + r->headers_out.etag_time = -1; + r->headers_out.etag_uniq = (ngx_file_uniq_t) -1; r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1; diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index f0e0ead..bae17ba 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -162,6 +162,7 @@ typedef struct { ngx_table_elt_t *host; ngx_table_elt_t *connection; ngx_table_elt_t *if_modified_since; + ngx_table_elt_t *if_match; ngx_table_elt_t *user_agent; ngx_table_elt_t *referer; ngx_table_elt_t *content_length; @@ -253,6 +254,10 @@ typedef struct { off_t content_length_n; time_t date_time; time_t last_modified_time; + + off_t etag_size; + time_t etag_time; + ngx_file_uniq_t etag_uniq; } ngx_http_headers_out_t; diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c index 8493268..025b4fb 100644 --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -585,6 +585,7 @@ ngx_http_send_special_response(ngx_http_request_t *r, ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); rc = ngx_http_send_header(r); @@ -683,6 +684,7 @@ ngx_http_send_refresh(ngx_http_request_t *r) ngx_http_clear_accept_ranges(r); ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); rc = ngx_http_send_header(r); diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index 998c4ce..41fedca 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -71,6 +71,8 @@ static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_sent_etag(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r, @@ -213,6 +215,9 @@ static ngx_http_variable_t ngx_http_core_variables[] = { { ngx_string("sent_http_last_modified"), NULL, ngx_http_variable_sent_last_modified, 0, 0, 0 }, + { ngx_string("sent_http_etag"), NULL, + ngx_http_variable_sent_etag, 0, 0, 0 }, + { ngx_string("sent_http_connection"), NULL, ngx_http_variable_sent_connection, 0, 0, 0 }, @@ -1308,6 +1313,50 @@ ngx_http_variable_sent_last_modified(ngx_http_request_t *r, static ngx_int_t +ngx_http_variable_sent_etag(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + + if (r->headers_out.etag) { + v->len = r->headers_out.etag->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_out.etag->value.data; + + return NGX_OK; + } + + if (r->headers_out.etag_size >= 0 && + r->headers_out.etag_time >= 0 && + (ngx_int_t)r->headers_out.etag_uniq >= 0) { + p = ngx_pnalloc(r->pool, + sizeof("ETag: ") - 1 + NGX_OFF_T_LEN + sizeof("_") - 1 + + NGX_TIME_T_LEN + sizeof("_") - 1 + NGX_INT_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(p, "ETag: %XO-%XM-%Xd", + r->headers_out.etag_size, + r->headers_out.etag_time, + r->headers_out.etag_uniq) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) {