Copyright (c) 2009, FRiCKLE Piotr Sikora All rights reserved. This project was fully funded by yo.se. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY FRiCKLE PIOTR SIKORA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FRiCKLE PIOTR SIKORA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --- src/http/modules/ngx_http_fastcgi_module.c.orig Tue Nov 17 06:21:25 2009 +++ src/http/modules/ngx_http_fastcgi_module.c Tue Nov 17 06:31:14 2009 @@ -7,6 +7,7 @@ #include #include #include +#include typedef struct { @@ -156,6 +157,9 @@ void *conf); static char *ngx_http_fastcgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_fastcgi_cache_purge(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_fastcgi_cache_purge_handler(ngx_http_request_t *r); #endif static char *ngx_http_fastcgi_lowat_check(ngx_conf_t *cf, void *post, @@ -368,6 +372,13 @@ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, + { ngx_string("fastcgi_cache_purge"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_http_fastcgi_cache_purge, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + #endif { ngx_string("fastcgi_temp_path"), @@ -2465,6 +2476,14 @@ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); +#if (NGX_HTTP_CACHE) + + if (clcf->handler == ngx_http_fastcgi_cache_purge_handler) { + return "is incompatible with \"fastcgi_cache_purge\""; + } + +#endif + clcf->handler = ngx_http_fastcgi_handler; if (clcf->name.data[clcf->name.len - 1] == '/') { @@ -2582,7 +2601,7 @@ if (flcf->upstream.cache != NGX_CONF_UNSET_PTR && flcf->upstream.cache != NULL) { - return "is incompatible with \"fastcgi_cache\""; + return "is incompatible with \"fastcgi_cache\" and \"fastcgi_cache_purge\""; } #endif @@ -2625,7 +2644,7 @@ value = cf->args->elts; if (flcf->upstream.cache != NGX_CONF_UNSET_PTR) { - return "is duplicate"; + return "is either duplicate or collides with \"fastcgi_cache_purge\""; } if (ngx_strcmp(value[1].data, "off") == 0) { @@ -2658,7 +2677,7 @@ value = cf->args->elts; if (flcf->cache_key.value.len) { - return "is duplicate"; + return "is either duplicate or collides with \"fastcgi_cache_purge\""; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -2672,6 +2691,144 @@ } return NGX_CONF_OK; +} + +static char * +ngx_http_fastcgi_cache_purge(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_fastcgi_loc_conf_t *flcf = conf; + + ngx_http_compile_complex_value_t ccv; + ngx_http_core_loc_conf_t *clcf; + ngx_str_t *value; + + /* check for duplicates / collisions */ + if (flcf->upstream.cache != NGX_CONF_UNSET_PTR + && flcf->upstream.cache != NULL) + { + return "is either duplicate or collides with \"fastcgi_cache\""; + } + + if (flcf->upstream.upstream || flcf->fastcgi_lengths) { + return "is incompatible with \"fastcgi_pass\""; + } + + if (flcf->upstream.store > 0 || flcf->upstream.store_lengths) { + return "is incompatible with \"fastcgi_store\""; + } + + value = cf->args->elts; + + /* set fastcgi_cache part */ + flcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0, + &ngx_http_fastcgi_module); + if (flcf->upstream.cache == NULL) { + return NGX_CONF_ERROR; + } + + /* set fastcgi_cache_key part */ + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[2]; + ccv.complex_value = &flcf->cache_key; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + /* set handler */ + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + clcf->handler = ngx_http_fastcgi_cache_purge_handler; + + return NGX_CONF_OK; +} + +static char ngx_http_cache_purge_success_page_top[] = +"" CRLF +"Successful purge" CRLF +"" CRLF +"

Successful purge

" CRLF +; + +static char ngx_http_cache_purge_success_page_tail[] = +CRLF "
" CRLF +"
" NGINX_VER "
" CRLF +"" CRLF +"" CRLF +; + +static ngx_int_t +ngx_http_fastcgi_cache_purge_handler(ngx_http_request_t *r) +{ + ngx_http_fastcgi_loc_conf_t *flcf; + ngx_chain_t out; + ngx_buf_t *b; + ngx_str_t *key; + ngx_int_t rc; + size_t len; + + if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_DELETE))) { + return NGX_HTTP_NOT_ALLOWED; + } + + flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); + + rc = ngx_http_file_cache_purge(r, flcf->upstream.cache->data, + &flcf->cache_key); + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } else if (rc == NGX_DECLINED) { + return NGX_HTTP_NOT_FOUND; + } + + key = r->cache->keys.elts; + + len = sizeof(ngx_http_cache_purge_success_page_top) - 1 + + sizeof(ngx_http_cache_purge_success_page_tail) - 1 + + sizeof("
Key : ") - 1 + sizeof(CRLF "
Path: ") - 1 + + key[0].len + r->cache->file.name.len; + + r->headers_out.content_type.len = sizeof("text/html") - 1; + r->headers_out.content_type.data = (u_char *) "text/html"; + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + if (r->method == NGX_HTTP_HEAD) { + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + out.buf = b; + out.next = NULL; + + b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_top, + sizeof(ngx_http_cache_purge_success_page_top) - 1); + b->last = ngx_cpymem(b->last, "
Key : ", sizeof("
Key : ") - 1); + b->last = ngx_cpymem(b->last, key[0].data, key[0].len); + b->last = ngx_cpymem(b->last, CRLF "
Path: ", + sizeof(CRLF "
Path: ") - 1); + b->last = ngx_cpymem(b->last, r->cache->file.name.data, + r->cache->file.name.len); + b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_tail, + sizeof(ngx_http_cache_purge_success_page_tail) - 1); + b->last_buf = 1; + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + return ngx_http_output_filter(r, &out); } #endif --- src/http/modules/ngx_http_proxy_module.c.orig Tue Nov 17 06:21:35 2009 +++ src/http/modules/ngx_http_proxy_module.c Tue Nov 17 06:31:19 2009 @@ -7,6 +7,7 @@ #include #include #include +#include typedef struct ngx_http_proxy_redirect_s ngx_http_proxy_redirect_t; @@ -138,6 +139,9 @@ void *conf); static char *ngx_http_proxy_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_proxy_cache_purge(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_proxy_cache_purge_handler(ngx_http_request_t *r); #endif static char *ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data); @@ -392,6 +396,13 @@ offsetof(ngx_http_proxy_loc_conf_t, upstream.cache_methods), &ngx_http_upstream_cache_method_mask }, + { ngx_string("proxy_cache_purge"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_http_proxy_cache_purge, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + #endif { ngx_string("proxy_temp_path"), @@ -2597,6 +2608,14 @@ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); +#if (NGX_HTTP_CACHE) + + if (clcf->handler == ngx_http_proxy_cache_purge_handler) { + return "is incompatible with \"proxy_cache_purge\""; + } + +#endif + clcf->handler = ngx_http_proxy_handler; if (clcf->name.data[clcf->name.len - 1] == '/') { @@ -2836,7 +2855,7 @@ if (plcf->upstream.cache != NGX_CONF_UNSET_PTR && plcf->upstream.cache != NULL) { - return "is incompatible with \"proxy_cache\""; + return "is incompatible with \"proxy_cache\" and \"proxy_cache_purge\""; } #endif @@ -2879,7 +2898,7 @@ value = cf->args->elts; if (plcf->upstream.cache != NGX_CONF_UNSET_PTR) { - return "is duplicate"; + return "is either duplicate or collides with \"proxy_cache_purge\""; } if (ngx_strcmp(value[1].data, "off") == 0) { @@ -2912,7 +2931,7 @@ value = cf->args->elts; if (plcf->cache_key.value.len) { - return "is duplicate"; + return "is either duplicate or collides with \"proxy_cache_purge\""; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -2926,6 +2945,144 @@ } return NGX_CONF_OK; +} + +static char * +ngx_http_proxy_cache_purge(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_proxy_loc_conf_t *plcf = conf; + + ngx_http_compile_complex_value_t ccv; + ngx_http_core_loc_conf_t *clcf; + ngx_str_t *value; + + /* check for duplicates / collisions */ + if (plcf->upstream.cache != NGX_CONF_UNSET_PTR + && plcf->upstream.cache != NULL) + { + return "is either duplicate or collides with \"proxy_cache\""; + } + + if (plcf->upstream.upstream || plcf->proxy_lengths) { + return "is incompatible with \"proxy_pass\""; + } + + if (plcf->upstream.store > 0 || plcf->upstream.store_lengths) { + return "is incompatible with \"proxy_store\""; + } + + value = cf->args->elts; + + /* set proxy_cache part */ + plcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0, + &ngx_http_proxy_module); + if (plcf->upstream.cache == NULL) { + return NGX_CONF_ERROR; + } + + /* set proxy_cache_key part */ + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[2]; + ccv.complex_value = &plcf->cache_key; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + /* set handler */ + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + clcf->handler = ngx_http_proxy_cache_purge_handler; + + return NGX_CONF_OK; +} + +static char ngx_http_cache_purge_success_page_top[] = +"" CRLF +"Successful purge" CRLF +"" CRLF +"

Successful purge

" CRLF +; + +static char ngx_http_cache_purge_success_page_tail[] = +CRLF "
" CRLF +"
" NGINX_VER "
" CRLF +"" CRLF +"" CRLF +; + +static ngx_int_t +ngx_http_proxy_cache_purge_handler(ngx_http_request_t *r) +{ + ngx_http_proxy_loc_conf_t *plcf; + ngx_chain_t out; + ngx_buf_t *b; + ngx_str_t *key; + ngx_int_t rc; + size_t len; + + if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_DELETE))) { + return NGX_HTTP_NOT_ALLOWED; + } + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + rc = ngx_http_file_cache_purge(r, plcf->upstream.cache->data, + &plcf->cache_key); + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } else if (rc == NGX_DECLINED) { + return NGX_HTTP_NOT_FOUND; + } + + key = r->cache->keys.elts; + + len = sizeof(ngx_http_cache_purge_success_page_top) - 1 + + sizeof(ngx_http_cache_purge_success_page_tail) - 1 + + sizeof("
Key : ") - 1 + sizeof(CRLF "
Path: ") - 1 + + key[0].len + r->cache->file.name.len; + + r->headers_out.content_type.len = sizeof("text/html") - 1; + r->headers_out.content_type.data = (u_char *) "text/html"; + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + if (r->method == NGX_HTTP_HEAD) { + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + out.buf = b; + out.next = NULL; + + b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_top, + sizeof(ngx_http_cache_purge_success_page_top) - 1); + b->last = ngx_cpymem(b->last, "
Key : ", sizeof("
Key : ") - 1); + b->last = ngx_cpymem(b->last, key[0].data, key[0].len); + b->last = ngx_cpymem(b->last, CRLF "
Path: ", + sizeof(CRLF "
Path: ") - 1); + b->last = ngx_cpymem(b->last, r->cache->file.name.data, + r->cache->file.name.len); + b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_tail, + sizeof(ngx_http_cache_purge_success_page_tail) - 1); + b->last_buf = 1; + + rc = ngx_http_send_header(r); + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + return ngx_http_output_filter(r, &out); } #endif --- src/http/ngx_http_cache.h.orig Tue Nov 17 06:20:50 2009 +++ src/http/ngx_http_cache.h Tue Nov 17 06:30:16 2009 @@ -134,6 +134,9 @@ char *ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +ngx_int_t ngx_http_file_cache_purge(ngx_http_request_t *r, + ngx_http_file_cache_t *cache, ngx_http_complex_value_t *cache_key); + extern ngx_str_t ngx_http_cache_status[]; --- src/http/ngx_http_file_cache.c.orig Tue Nov 17 06:21:00 2009 +++ src/http/ngx_http_file_cache.c Tue Nov 17 07:50:00 2009 @@ -1620,6 +1620,90 @@ return NGX_CONF_OK; } +ngx_int_t +ngx_http_file_cache_purge(ngx_http_request_t *r, ngx_http_file_cache_t *cache, + ngx_http_complex_value_t *cache_key) +{ + ngx_http_cache_t *c; + ngx_str_t *key; + ngx_int_t rc; + + rc = ngx_http_discard_request_body(r); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t)); + if (c == NULL) { + return NGX_ERROR; + } + + rc = ngx_array_init(&c->keys, r->pool, 1, sizeof(ngx_str_t)); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + key = ngx_array_push(&c->keys); + if (key == NULL) { + return NGX_ERROR; + } + + rc = ngx_http_complex_value(r, cache_key, key); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + r->cache = c; + c->body_start = ngx_pagesize; + c->file_cache = cache; + c->file.log = r->connection->log; + + ngx_http_file_cache_create_key(r); + + rc = ngx_http_file_cache_open(r); + if (rc == NGX_HTTP_CACHE_UPDATING || rc == NGX_HTTP_CACHE_STALE) { + rc = NGX_OK; + } + + if (rc != NGX_OK) { + if (rc == NGX_DECLINED) { + return rc; + } else { + return NGX_ERROR; + } + } + + /* + * delete file from disk but *keep* in-memory node, + * because other requests might still point to it. + */ + + ngx_shmtx_lock(&cache->shpool->mutex); + + if (!c->node->exists) { + /* race between concurrent purges, backoff */ + ngx_shmtx_unlock(&cache->shpool->mutex); + return NGX_DECLINED; + } + + cache->sh->size -= (c->node->length + cache->bsize - 1) / cache->bsize; + c->node->exists = 0; + + ngx_shmtx_unlock(&cache->shpool->mutex); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "http file cache purge: \"%s\"", c->file.name.data); + + if (ngx_delete_file(c->file.name.data) == NGX_FILE_ERROR) { + /* entry in error log is enough, don't notice client */ + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", c->file.name.data); + } + + /* file deleted from cache */ + return NGX_OK; +} + char * ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,