diff --git a/README.md b/README.md index 3b42e32..92f3cd3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Configuration directives (same location syntax) =============================================== fastcgi_cache_purge ------------------- -* **syntax**: `fastcgi_cache_purge on|off| [from all| [.. ]]` +* **syntax**: `fastcgi_cache_purge on|off| [purge_all] [from all| [.. ]]` * **default**: `none` * **context**: `http`, `server`, `location` @@ -27,7 +27,7 @@ Allow purging of selected pages from `FastCGI`'s cache. proxy_cache_purge ----------------- -* **syntax**: `proxy_cache_purge on|off| [from all| [.. ]]` +* **syntax**: `proxy_cache_purge on|off| [purge_all] [from all| [.. ]]` * **default**: `none` * **context**: `http`, `server`, `location` @@ -36,7 +36,7 @@ Allow purging of selected pages from `proxy`'s cache. scgi_cache_purge ---------------- -* **syntax**: `scgi_cache_purge on|off| [from all| [.. ]]` +* **syntax**: `scgi_cache_purge on|off| [purge_all] [from all| [.. ]]` * **default**: `none` * **context**: `http`, `server`, `location` @@ -45,7 +45,7 @@ Allow purging of selected pages from `SCGI`'s cache. uwsgi_cache_purge ----------------- -* **syntax**: `uwsgi_cache_purge on|off| [from all| [.. ]]` +* **syntax**: `uwsgi_cache_purge on|off| [purge_all] [from all| [.. ]]` * **default**: `none` * **context**: `http`, `server`, `location` @@ -106,6 +106,22 @@ Sample configuration (same location syntax) } +Sample configuration (same location syntax - purge all cached files) +==================================================================== + http { + proxy_cache_path /tmp/cache keys_zone=tmpcache:10m; + + server { + location / { + proxy_pass http://127.0.0.1:8000; + proxy_cache tmpcache; + proxy_cache_key $uri$is_args$args; + proxy_cache_purge PURGE purge_all from 127.0.0.1; + } + } + } + + Sample configuration (separate location syntax) =============================================== http { diff --git a/ngx_cache_purge_module.c b/ngx_cache_purge_module.c index 62d3818..d4fd835 100644 --- a/ngx_cache_purge_module.c +++ b/ngx_cache_purge_module.c @@ -43,6 +43,7 @@ typedef struct { ngx_flag_t enable; ngx_str_t method; + ngx_flag_t purge_all; ngx_array_t *access; /* array of ngx_in_cidr_t */ ngx_array_t *access6; /* array of ngx_in6_cidr_t */ } ngx_http_cache_purge_conf_t; @@ -90,6 +91,11 @@ char *ngx_http_uwsgi_cache_purge_conf(ngx_conf_t *cf, ngx_int_t ngx_http_uwsgi_cache_purge_handler(ngx_http_request_t *r); # endif /* NGX_HTTP_UWSGI */ +static ngx_int_t +ngx_http_purge_file_cache_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path); +static ngx_int_t +ngx_http_purge_file_cache_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path); + ngx_int_t ngx_http_cache_purge_access_handler(ngx_http_request_t *r); ngx_int_t ngx_http_cache_purge_access(ngx_array_t *a, ngx_array_t *a6, struct sockaddr *s); @@ -105,6 +111,9 @@ void ngx_http_cache_purge_handler(ngx_http_request_t *r); ngx_int_t ngx_http_file_cache_purge(ngx_http_request_t *r); + +void ngx_http_cache_purge_all(ngx_http_request_t *r, ngx_http_file_cache_t *cache); + char *ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf); @@ -378,11 +387,12 @@ ngx_http_fastcgi_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, ngx_int_t ngx_http_fastcgi_cache_purge_handler(ngx_http_request_t *r) { - ngx_http_file_cache_t *cache; - ngx_http_fastcgi_loc_conf_t *flcf; + ngx_http_file_cache_t *cache; + ngx_http_fastcgi_loc_conf_t *flcf; + ngx_http_cache_purge_loc_conf_t *cplcf; # if (nginx_version >= 1007009) - ngx_http_fastcgi_main_conf_t *fmcf; - ngx_int_t rc; + ngx_http_fastcgi_main_conf_t *fmcf; + ngx_int_t rc; # endif /* nginx_version >= 1007009 */ if (ngx_http_upstream_create(r) != NGX_OK) { @@ -414,6 +424,12 @@ ngx_http_fastcgi_cache_purge_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + // Purge-all option + cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module); + if (cplcf->conf->purge_all) { + ngx_http_cache_purge_all(r, cache); + } + # if (nginx_version >= 8011) r->main->count++; # endif @@ -649,11 +665,12 @@ ngx_http_proxy_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_int_t ngx_http_proxy_cache_purge_handler(ngx_http_request_t *r) { - ngx_http_file_cache_t *cache; - ngx_http_proxy_loc_conf_t *plcf; + ngx_http_file_cache_t *cache; + ngx_http_proxy_loc_conf_t *plcf; + ngx_http_cache_purge_loc_conf_t *cplcf; # if (nginx_version >= 1007009) - ngx_http_proxy_main_conf_t *pmcf; - ngx_int_t rc; + ngx_http_proxy_main_conf_t *pmcf; + ngx_int_t rc; # endif /* nginx_version >= 1007009 */ if (ngx_http_upstream_create(r) != NGX_OK) { @@ -685,6 +702,12 @@ ngx_http_proxy_cache_purge_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + // Purge-all option + cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module); + if (cplcf->conf->purge_all) { + ngx_http_cache_purge_all(r, cache); + } + # if (nginx_version >= 8011) r->main->count++; # endif @@ -862,11 +885,12 @@ ngx_http_scgi_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_int_t ngx_http_scgi_cache_purge_handler(ngx_http_request_t *r) { - ngx_http_file_cache_t *cache; - ngx_http_scgi_loc_conf_t *slcf; + ngx_http_file_cache_t *cache; + ngx_http_scgi_loc_conf_t *slcf; + ngx_http_cache_purge_loc_conf_t *cplcf; # if (nginx_version >= 1007009) - ngx_http_scgi_main_conf_t *smcf; - ngx_int_t rc; + ngx_http_scgi_main_conf_t *smcf; + ngx_int_t rc; # endif /* nginx_version >= 1007009 */ if (ngx_http_upstream_create(r) != NGX_OK) { @@ -898,6 +922,12 @@ ngx_http_scgi_cache_purge_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + // Purge-all option + cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module); + if (cplcf->conf->purge_all) { + ngx_http_cache_purge_all(r, cache); + } + # if (nginx_version >= 8011) r->main->count++; # endif @@ -1098,11 +1128,12 @@ ngx_http_uwsgi_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_int_t ngx_http_uwsgi_cache_purge_handler(ngx_http_request_t *r) { - ngx_http_file_cache_t *cache; - ngx_http_uwsgi_loc_conf_t *ulcf; + ngx_http_file_cache_t *cache; + ngx_http_uwsgi_loc_conf_t *ulcf; + ngx_http_cache_purge_loc_conf_t *cplcf; # if (nginx_version >= 1007009) - ngx_http_uwsgi_main_conf_t *umcf; - ngx_int_t rc; + ngx_http_uwsgi_main_conf_t *umcf; + ngx_int_t rc; # endif /* nginx_version >= 1007009 */ if (ngx_http_upstream_create(r) != NGX_OK) { @@ -1134,6 +1165,12 @@ ngx_http_uwsgi_cache_purge_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + // Purge-all option + cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module); + if (cplcf->conf->purge_all) { + ngx_http_cache_purge_all(r, cache); + } + # if (nginx_version >= 8011) r->main->count++; # endif @@ -1144,6 +1181,27 @@ ngx_http_uwsgi_cache_purge_handler(ngx_http_request_t *r) } # endif /* NGX_HTTP_UWSGI */ + +static ngx_int_t +ngx_http_purge_file_cache_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + return NGX_OK; +} + +static ngx_int_t +ngx_http_purge_file_cache_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, + "http file cache delete: \"%s\"", path->data); + + if (ngx_delete_file(path->data) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno, + ngx_delete_file_n " \"%s\" failed", path->data); + } + + return NGX_OK; +} + ngx_int_t ngx_http_cache_purge_access_handler(ngx_http_request_t *r) { @@ -1401,6 +1459,7 @@ ngx_http_cache_purge_init(ngx_http_request_t *r, ngx_http_file_cache_t *cache, void ngx_http_cache_purge_handler(ngx_http_request_t *r) { + ngx_http_cache_purge_loc_conf_t *cplcf; ngx_int_t rc; # if (NGX_HAVE_FILE_AIO) @@ -1409,11 +1468,17 @@ ngx_http_cache_purge_handler(ngx_http_request_t *r) } # endif - rc = ngx_http_file_cache_purge(r); + cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module); + if (cplcf->conf->purge_all) { + rc = NGX_OK; + } + else { + rc = ngx_http_file_cache_purge(r); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http file cache purge: %i, \"%s\"", rc, r->cache->file.name.data); + } switch (rc) { case NGX_OK: @@ -1499,6 +1564,27 @@ ngx_http_file_cache_purge(ngx_http_request_t *r) return NGX_OK; } + +void +ngx_http_cache_purge_all(ngx_http_request_t *r, ngx_http_file_cache_t *cache) { + ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "purge_all http in %s", + cache->path->name.data); + + // Walk the tree and remove all the files + ngx_tree_ctx_t tree; + tree.init_handler = NULL; + tree.file_handler = ngx_http_purge_file_cache_delete_file; + tree.pre_tree_handler = ngx_http_purge_file_cache_noop; + tree.post_tree_handler = ngx_http_purge_file_cache_noop; + tree.spec_handler = ngx_http_purge_file_cache_noop; + tree.data = cache; + tree.alloc = 0; + tree.log = ngx_cycle->log; + + ngx_walk_tree(&tree, &cache->path->name); +} + char * ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf) { @@ -1510,7 +1596,11 @@ ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf) ngx_str_t *value; ngx_int_t rc; ngx_uint_t i; + ngx_uint_t from_position; + from_position = 2; + + // xxx_cache_purge on|off| [purge_all] [from all| [.. ]] value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { @@ -1529,20 +1619,27 @@ ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf) return NGX_CONF_OK; } + // We will purge all the keys + if (ngx_strcmp(value[from_position].data, "purge_all") == 0) { + cpcf->purge_all = 1; + from_position++; + } + + /* sanity check */ - if (ngx_strcmp(value[2].data, "from") != 0) { + if (ngx_strcmp(value[from_position].data, "from") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\", expected" - " \"from\" keyword", &value[2]); + " \"from\" keyword", &value[from_position]); return NGX_CONF_ERROR; } - if (ngx_strcmp(value[3].data, "all") == 0) { + if (ngx_strcmp(value[from_position + 1].data, "all") == 0) { cpcf->enable = 1; return NGX_CONF_OK; } - for (i = 3; i < cf->args->nelts; i++) { + for (i = (from_position + 1); i < cf->args->nelts; i++) { rc = ngx_ptocidr(&value[i], &cidr); if (rc == NGX_ERROR) { @@ -1560,7 +1657,7 @@ ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf) switch (cidr.family) { case AF_INET: if (cpcf->access == NULL) { - cpcf->access = ngx_array_create(cf->pool, cf->args->nelts - 3, + cpcf->access = ngx_array_create(cf->pool, cf->args->nelts - (from_position + 1), sizeof(ngx_in_cidr_t)); if (cpcf->access == NULL) { return NGX_CONF_ERROR; @@ -1580,7 +1677,7 @@ ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf) # if (NGX_HAVE_INET6) case AF_INET6: if (cpcf->access6 == NULL) { - cpcf->access6 = ngx_array_create(cf->pool, cf->args->nelts - 3, + cpcf->access6 = ngx_array_create(cf->pool, cf->args->nelts - (from_position + 1), sizeof(ngx_in6_cidr_t)); if (cpcf->access6 == NULL) { return NGX_CONF_ERROR; @@ -1613,6 +1710,7 @@ ngx_http_cache_purge_merge_conf(ngx_http_cache_purge_conf_t *conf, if (prev->enable == 1) { conf->enable = prev->enable; conf->method = prev->method; + conf->purge_all = prev->purge_all; conf->access = prev->access; conf->access6 = prev->access6; diff --git a/t/proxy3.t b/t/proxy3.t new file mode 100644 index 0000000..ebd7c82 --- /dev/null +++ b/t/proxy3.t @@ -0,0 +1,140 @@ +# vi:filetype=perl + +use lib 'lib'; +use Test::Nginx::Socket; + +repeat_each(1); + +plan tests => 32; + +our $http_config = <<'_EOC_'; + proxy_cache_path /tmp/ngx_cache_purge_cache keys_zone=test_cache:10m; + proxy_temp_path /tmp/ngx_cache_purge_temp 1 2; +_EOC_ + +our $config = <<'_EOC_'; + location /proxy { + proxy_pass $scheme://127.0.0.1:$server_port/etc/passwd; + proxy_cache test_cache; + proxy_cache_key $uri$is_args$args; + proxy_cache_valid 3m; + add_header X-Cache-Status $upstream_cache_status; + + proxy_cache_purge PURGE purge_all from 1.0.0.0/8 127.0.0.0/8 3.0.0.0/8; + } + + + location = /etc/passwd { + root /var/www/html; + } +_EOC_ + +worker_connections(128); +no_shuffle(); +run_tests(); + +no_diff(); + +__DATA__ + +=== TEST 1: prepare passwd +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/passwd +--- error_code: 200 +--- response_headers +Content-Type: text/plain +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + +=== TEST 2: prepare shadow +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/shadow +--- error_code: 200 +--- response_headers +Content-Type: text/plain +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + + +=== TEST 3: get from cache passwd +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/passwd +--- error_code: 200 +--- response_headers +Content-Type: text/plain +X-Cache-Status: HIT +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + +=== TEST 4: get from cache shadow +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/shadow +--- error_code: 200 +--- response_headers +Content-Type: text/plain +X-Cache-Status: HIT +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + +=== TEST 5: purge from cache +--- http_config eval: $::http_config +--- config eval: $::config +--- request +PURGE /proxy/any +--- error_code: 200 +--- response_headers +Content-Type: text/html +--- response_body_like: Successful purge +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + +=== TEST 6: get from empty cache passwd +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/passwd +--- error_code: 200 +--- response_headers +Content-Type: text/plain +X-Cache-Status: MISS +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/ + + +=== TEST 7: get from empty cache shadow +--- http_config eval: $::http_config +--- config eval: $::config +--- request +GET /proxy/shadow +--- error_code: 200 +--- response_headers +Content-Type: text/plain +X-Cache-Status: MISS +--- response_body_like: root +--- timeout: 10 +--- no_error_log eval +qr/\[(warn|error|crit|alert|emerg)\]/