├── README ├── config └── ngx_http_dynamic_etags_module.c /README: -------------------------------------------------------------------------------- 1 | Attempt at handling ETag / If-None-Match on proxied content. 2 | 3 | I plan on using this to front a Varnish server using a lot of ESI. 4 | 5 | It does kind of work, but... be aware, this is my first attempt at developping 6 | a nginx plugin, and dealing with headers after having read the body was not 7 | exactly in the how-to. 8 | 9 | Any comment and/or improvement and/or fork is welcome. 10 | 11 | Thanks to http://github.com/kkung/nginx-static-etags/ for... inspiration. 12 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_dynamic_etags_module 2 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_dynamic_etags_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_dynamic_etags_module.c" 4 | -------------------------------------------------------------------------------- /ngx_http_dynamic_etags_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Mathieu Poumeyrol ( http://github.com/kali ) 3 | * 4 | * All rights reserved. 5 | * All original code was written by Mike West ( http://mikewest.org/ ) and 6 | Adrian Jung ( http://me2day.net/kkung, kkungkkung@gmail.com ). 7 | * 8 | * Copyright 2008 Mike West ( http://mikewest.org/ ) 9 | * Copyright 2009 Adrian Jung ( http://me2day.net/kkung, kkungkkung@gmail.com ). 10 | * 11 | * The following is released under the Creative Commons BSD license, 12 | * available for your perusal at `http://creativecommons.org/licenses/BSD/` 13 | */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | typedef struct { 21 | ngx_flag_t enable; 22 | } ngx_http_dynamic_etags_loc_conf_t; 23 | 24 | typedef struct { 25 | ngx_flag_t done; 26 | } ngx_http_dynamic_etags_module_ctx_t; 27 | 28 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 29 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 30 | 31 | static ngx_uint_t if_match(ngx_http_request_t *r, ngx_table_elt_t *header); 32 | static void * ngx_http_dynamic_etags_create_loc_conf(ngx_conf_t *cf); 33 | static char * ngx_http_dynamic_etags_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 34 | static ngx_int_t ngx_http_dynamic_etags_init(ngx_conf_t *cf); 35 | static ngx_int_t ngx_http_dynamic_etags_header_filter(ngx_http_request_t *r); 36 | static ngx_int_t ngx_http_dynamic_etags_body_filter(ngx_http_request_t *r, ngx_chain_t *in); 37 | 38 | static ngx_command_t ngx_http_dynamic_etags_commands[] = { 39 | { ngx_string( "dynamic_etags" ), 40 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 41 | ngx_conf_set_flag_slot, 42 | NGX_HTTP_LOC_CONF_OFFSET, 43 | offsetof( ngx_http_dynamic_etags_loc_conf_t, enable ), 44 | NULL }, 45 | ngx_null_command 46 | }; 47 | 48 | static ngx_http_module_t ngx_http_dynamic_etags_module_ctx = { 49 | NULL, /* preconfiguration */ 50 | ngx_http_dynamic_etags_init, /* postconfiguration */ 51 | 52 | NULL, /* create main configuration */ 53 | NULL, /* init main configuration */ 54 | 55 | NULL, /* create server configuration */ 56 | NULL, /* merge server configuration */ 57 | 58 | ngx_http_dynamic_etags_create_loc_conf, /* create location configuration */ 59 | ngx_http_dynamic_etags_merge_loc_conf, /* merge location configuration */ 60 | }; 61 | 62 | ngx_module_t ngx_http_dynamic_etags_module = { 63 | NGX_MODULE_V1, 64 | &ngx_http_dynamic_etags_module_ctx, /* module context */ 65 | ngx_http_dynamic_etags_commands, /* module directives */ 66 | NGX_HTTP_MODULE, /* module type */ 67 | NULL, /* init master */ 68 | NULL, /* init module */ 69 | NULL, /* init process */ 70 | NULL, /* init thread */ 71 | NULL, /* exit thread */ 72 | NULL, /* exit process */ 73 | NULL, /* exit master */ 74 | NGX_MODULE_V1_PADDING 75 | }; 76 | 77 | static void * ngx_http_dynamic_etags_create_loc_conf(ngx_conf_t *cf) { 78 | ngx_http_dynamic_etags_loc_conf_t *conf; 79 | 80 | conf = ngx_pcalloc( cf->pool, sizeof( ngx_http_dynamic_etags_loc_conf_t ) ); 81 | if ( NULL == conf ) { 82 | return NGX_CONF_ERROR; 83 | } 84 | conf->enable = NGX_CONF_UNSET_UINT; 85 | return conf; 86 | } 87 | 88 | static char * ngx_http_dynamic_etags_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { 89 | ngx_http_dynamic_etags_loc_conf_t *prev = parent; 90 | ngx_http_dynamic_etags_loc_conf_t *conf = child; 91 | 92 | ngx_conf_merge_value( conf->enable, prev->enable, 0 ); 93 | 94 | return NGX_CONF_OK; 95 | } 96 | 97 | static ngx_int_t ngx_http_dynamic_etags_init(ngx_conf_t *cf) { 98 | ngx_http_next_header_filter = ngx_http_top_header_filter; 99 | ngx_http_top_header_filter = ngx_http_dynamic_etags_header_filter; 100 | 101 | ngx_http_next_body_filter = ngx_http_top_body_filter; 102 | ngx_http_top_body_filter = ngx_http_dynamic_etags_body_filter; 103 | 104 | return NGX_OK; 105 | } 106 | 107 | static ngx_int_t ngx_http_dynamic_etags_header_filter(ngx_http_request_t *r) { 108 | 109 | ngx_http_dynamic_etags_module_ctx_t *ctx; 110 | 111 | ctx = ngx_http_get_module_ctx(r, ngx_http_dynamic_etags_module); 112 | 113 | if (ctx) { 114 | return ngx_http_next_header_filter(r); 115 | } 116 | 117 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_dynamic_etags_module_ctx_t)); 118 | if (ctx == NULL) { 119 | return NGX_ERROR; 120 | } 121 | 122 | ngx_http_set_ctx(r, ctx, ngx_http_dynamic_etags_module); 123 | 124 | ngx_http_clear_content_length(r); 125 | ngx_http_clear_accept_ranges(r); 126 | 127 | r->main_filter_need_in_memory = 1; 128 | r->filter_need_in_memory = 1; 129 | 130 | return NGX_OK; 131 | } 132 | 133 | static u_char hex[] = "0123456789abcdef"; 134 | 135 | static ngx_int_t ngx_http_dynamic_etags_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { 136 | ngx_chain_t *chain_link; 137 | ngx_http_dynamic_etags_module_ctx_t *ctx; 138 | 139 | ngx_int_t rc; 140 | ngx_md5_t md5; 141 | unsigned char digest[16]; 142 | ngx_uint_t i; 143 | 144 | ctx = ngx_http_get_module_ctx(r, ngx_http_dynamic_etags_module); 145 | if (ctx == NULL) { 146 | return ngx_http_next_body_filter(r, in); 147 | } 148 | 149 | ngx_http_dynamic_etags_loc_conf_t *loc_conf; 150 | loc_conf = ngx_http_get_module_loc_conf(r, ngx_http_dynamic_etags_module); 151 | if (1 == loc_conf->enable) { 152 | ngx_md5_init(&md5); 153 | for (chain_link = in; chain_link; chain_link = chain_link->next) { 154 | ngx_md5_update(&md5, chain_link->buf->pos, 155 | chain_link->buf->last - chain_link->buf->pos); 156 | } 157 | ngx_md5_final(digest, &md5); 158 | 159 | unsigned char* etag = ngx_pcalloc(r->pool, 34); 160 | etag[0] = etag[33] = '"'; 161 | for ( i = 0 ; i < 16; i++ ) { 162 | etag[2*i+1] = hex[digest[i] >> 4]; 163 | etag[2*i+2] = hex[digest[i] & 0xf]; 164 | } 165 | 166 | if(!r->headers_out.etag) { 167 | r->headers_out.etag = ngx_list_push(&r->headers_out.headers); 168 | } 169 | 170 | r->headers_out.etag->hash = 1; 171 | r->headers_out.etag->key.len = sizeof("ETag") - 1; 172 | r->headers_out.etag->key.data = (u_char *) "ETag"; 173 | r->headers_out.etag->value.len = 34; 174 | r->headers_out.etag->value.data = etag; 175 | 176 | /* look for If-None-Match in request headers */ 177 | ngx_uint_t found=0; 178 | ngx_list_part_t *part = NULL; 179 | ngx_table_elt_t *header = NULL; 180 | ngx_table_elt_t *if_none_match; 181 | part = &r->headers_in.headers.part; 182 | header = part->elts; 183 | for ( i = 0 ; ; i++ ) { 184 | if ( i >= part->nelts) { 185 | if ( part->next == NULL ) { 186 | break; 187 | } 188 | 189 | part = part->next; 190 | header = part->elts; 191 | i = 0; 192 | } 193 | 194 | if ( ngx_strcmp(header[i].key.data, "If-None-Match") == 0 ) { 195 | if_none_match = &header[i]; 196 | found = 1; 197 | break; 198 | } 199 | } 200 | 201 | if ( found ) { 202 | if (if_match(r, if_none_match)) { 203 | 204 | r->headers_out.status = NGX_HTTP_NOT_MODIFIED; 205 | r->headers_out.status_line.len = 0; 206 | r->headers_out.content_type.len = 0; 207 | ngx_http_clear_content_length(r); 208 | ngx_http_clear_accept_ranges(r); 209 | } 210 | } 211 | } 212 | 213 | 214 | rc = ngx_http_next_header_filter(r); 215 | if (rc == NGX_ERROR || rc > NGX_OK) { 216 | return NGX_ERROR; 217 | } 218 | 219 | ngx_http_set_ctx(r, NULL, ngx_http_dynamic_etags_module); 220 | 221 | return ngx_http_next_body_filter(r, in); 222 | } 223 | 224 | static ngx_uint_t if_match(ngx_http_request_t *r, ngx_table_elt_t *header) 225 | { 226 | u_char *start, *end, ch; 227 | ngx_str_t etag, *list; 228 | 229 | list = &header->value; 230 | 231 | if (list->len == 1 && list->data[0] == '*') { 232 | return 1; 233 | } 234 | 235 | if (r->headers_out.etag == NULL) { 236 | return 0; 237 | } 238 | 239 | etag = r->headers_out.etag->value; 240 | 241 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 242 | "http im:\"%V\" etag:%V", list, &etag); 243 | 244 | if ( etag.len > 2 245 | && etag.data[0] == 'W' 246 | && etag.data[1] == '/') 247 | { 248 | etag.len -= 2; 249 | etag.data += 2; 250 | } 251 | 252 | start = list->data; 253 | end = list->data + list->len; 254 | 255 | while (start < end) { 256 | 257 | if ( end - start > 2 258 | && start[0] == 'W' 259 | && start[1] == '/') 260 | { 261 | start += 2; 262 | } 263 | 264 | if (etag.len > (size_t) (end - start)) { 265 | return 0; 266 | } 267 | 268 | if (ngx_strncmp(start, etag.data, etag.len) != 0) { 269 | goto skip; 270 | } 271 | 272 | start += etag.len; 273 | 274 | while (start < end) { 275 | ch = *start; 276 | 277 | if (ch == ' ' || ch == '\t') { 278 | start++; 279 | continue; 280 | } 281 | 282 | break; 283 | } 284 | 285 | if (start == end || *start == ',') { 286 | return 1; 287 | } 288 | 289 | skip: 290 | 291 | while (start < end && *start != ',') { start++; } 292 | while (start < end) { 293 | ch = *start; 294 | 295 | if (ch == ' ' || ch == '\t' || ch == ',') { 296 | start++; 297 | continue; 298 | } 299 | 300 | break; 301 | } 302 | } 303 | 304 | return 0; 305 | } 306 | --------------------------------------------------------------------------------