leevis.com
leevis.com copied to clipboard
ngx_http_cache_purge_module 代码分析
概述
http_proxy提供了缓存功能,开源版本并没有提供缓存删除。第三方的http_cache_purge提供了缓存删除功能,该模块提供了2种删除缓存的配置,一种是:
proxy_cache_purge on|off|<method> [from all|<ip> [.. <ip>]]。另一种是:proxy_cache_purge zone_name key。
http {
proxy_cache_path /tmp/cache levels=1:2 keys_zone=test_cache:10m max_size=1g;
proxy_cache_valid 200 302 30m;
proxy_cache_valid 301 404 1m;
......
server {
......
# proxy_cache_purge on from all;
location / {
proxy_cache test_cache;
proxy_cache_key $scheme$host$uri?$args;
......
}
location ~ /purge(/.*) {
proxy_cache_purge test_cache $scheme$host$1?$args;
}
}
}
源码
该模块提供了FASTCGI、PROXY、SCGI、UWSGI的缓存删除,我们只看proxy的。 只提供了loc级别配置的结构体。
typedef struct {
ngx_flag_t enable;
ngx_str_t method; // purge 或者proxy_cache_purge指定的其他方法
ngx_array_t *access; /* array of ngx_in_cidr_t */ // from 的配置
ngx_array_t *access6; /* array of ngx_in6_cidr_t */
} ngx_http_cache_purge_conf_t;
typedef struct {
......
# if (NGX_HTTP_PROXY)
ngx_http_cache_purge_conf_t proxy;
# endif /* NGX_HTTP_PROXY */
......
ngx_http_cache_purge_conf_t *conf;
ngx_http_handler_pt handler;
ngx_http_handler_pt original_handler;
} ngx_http_cache_purge_loc_conf_t;
static ngx_http_module_t ngx_http_cache_purge_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_cache_purge_create_loc_conf, /* create location configuration */
ngx_http_cache_purge_merge_loc_conf /* merge location configuration */
};
void *
ngx_http_cache_purge_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_cache_purge_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_cache_purge_loc_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->*.method = { 0, NULL }
* conf->*.access = NULL
* conf->*.access6 = NULL
* conf->handler = NULL
* conf->original_handler = NULL
*/
......
# if (NGX_HTTP_PROXY)
conf->proxy.enable = NGX_CONF_UNSET;
# endif /* NGX_HTTP_PROXY */
......
conf->conf = NGX_CONF_UNSET_PTR;
return conf;
}
// 解析配置
char *
ngx_http_proxy_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_compile_complex_value_t ccv;
ngx_http_cache_purge_loc_conf_t *cplcf;
ngx_http_core_loc_conf_t *clcf;
ngx_http_proxy_loc_conf_t *plcf;
ngx_str_t *value;
# if (nginx_version >= 1007009)
ngx_http_complex_value_t cv;
# endif /* nginx_version >= 1007009 */
cplcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_cache_purge_module);
/* check for duplicates / collisions */
if (cplcf->proxy.enable != NGX_CONF_UNSET) {
return "is duplicate";
}
if (cf->args->nelts != 3) {
// proxy_cache_purge on|off|<method> [from all|<ip> [.. <ip>]]
return ngx_http_cache_purge_conf(cf, &cplcf->proxy);
}
// proxy_cache_purge zone_name key
if (cf->cmd_type & (NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF)) {
return "(separate location syntax) is not allowed here";
}
plcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_proxy_module);
# if (nginx_version >= 1007009)
if (plcf->upstream.cache > 0)
# else
if (plcf->upstream.cache != NGX_CONF_UNSET_PTR
&& plcf->upstream.cache != NULL)
# endif /* nginx_version >= 1007009 */
{
// proxy_cache_purge zone_name key 格式的配置,不允许和proxy_cache 出现在同一个location。
return "is incompatible with \"proxy_cache\"";
}
if (plcf->upstream.upstream || plcf->proxy_lengths) {
return "is incompatible with \"proxy_pass\"";
}
if (plcf->upstream.store > 0
# if (nginx_version < 1007009)
|| plcf->upstream.store_lengths
# endif /* nginx_version >= 1007009 */
)
{
return "is incompatible with \"proxy_store\"";
}
value = cf->args->elts;
/* set proxy_cache part */
# if (nginx_version >= 1007009)
plcf->upstream.cache = 1;
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (cv.lengths != NULL) {
plcf->upstream.cache_value = ngx_palloc(cf->pool,
sizeof(ngx_http_complex_value_t));
if (plcf->upstream.cache_value == NULL) {
return NGX_CONF_ERROR;
}
*plcf->upstream.cache_value = cv;
} else {
plcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
&ngx_http_proxy_module);
if (plcf->upstream.cache_zone == NULL) {
return NGX_CONF_ERROR;
}
}
# else
plcf->upstream.cache = ngx_shared_memory_add(cf, &value[1], 0,
&ngx_http_proxy_module);
if (plcf->upstream.cache == NULL) {
return NGX_CONF_ERROR;
}
# endif /* nginx_version >= 1007009 */
/* 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);
cplcf->proxy.enable = 0;
clcf->handler = ngx_http_proxy_cache_purge_handler;
return NGX_CONF_OK;
}
// 该解析函数只解析了删除缓存的method和可以删除缓存的客户端ip。
// 删除缓存的handler是在merge loc函数里设置的
char *
ngx_http_cache_purge_conf(ngx_conf_t *cf, ngx_http_cache_purge_conf_t *cpcf)
{
ngx_cidr_t cidr;
ngx_in_cidr_t *access;
# if (NGX_HAVE_INET6)
ngx_in6_cidr_t *access6;
# endif /* NGX_HAVE_INET6 */
ngx_str_t *value;
ngx_int_t rc;
ngx_uint_t i;
value = cf->args->elts;
if (ngx_strcmp(value[1].data, "off") == 0) {
cpcf->enable = 0;
return NGX_CONF_OK;
} else if (ngx_strcmp(value[1].data, "on") == 0) {
ngx_str_set(&cpcf->method, "PURGE");
} else {
// 删除缓存需要指定的method
cpcf->method = value[1];
}
if (cf->args->nelts < 4) {
cpcf->enable = 1;
return NGX_CONF_OK;
}
/* sanity check */
if (ngx_strcmp(value[2].data, "from") != 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\", expected"
" \"from\" keyword", &value[2]);
return NGX_CONF_ERROR;
}
if (ngx_strcmp(value[3].data, "all") == 0) {
cpcf->enable = 1;
return NGX_CONF_OK;
}
for (i = 3; i < cf->args->nelts; i++) {
rc = ngx_ptocidr(&value[i], &cidr);
if (rc == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
if (rc == NGX_DONE) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"low address bits of %V are meaningless",
&value[i]);
}
switch (cidr.family) {
case AF_INET:
if (cpcf->access == NULL) {
cpcf->access = ngx_array_create(cf->pool, cf->args->nelts - 3,
sizeof(ngx_in_cidr_t));
if (cpcf->access == NULL) {
return NGX_CONF_ERROR;
}
}
access = ngx_array_push(cpcf->access);
if (access == NULL) {
return NGX_CONF_ERROR;
}
access->mask = cidr.u.in.mask;
access->addr = cidr.u.in.addr;
break;
# if (NGX_HAVE_INET6)
case AF_INET6:
if (cpcf->access6 == NULL) {
cpcf->access6 = ngx_array_create(cf->pool, cf->args->nelts - 3,
sizeof(ngx_in6_cidr_t));
if (cpcf->access6 == NULL) {
return NGX_CONF_ERROR;
}
}
access6 = ngx_array_push(cpcf->access6);
if (access6 == NULL) {
return NGX_CONF_ERROR;
}
access6->mask = cidr.u.in6.mask;
access6->addr = cidr.u.in6.addr;
break;
# endif /* NGX_HAVE_INET6 */
}
}
cpcf->enable = 1;
return NGX_CONF_OK;
}
解析完配置文件后,调用ngx_http_cache_purge_merge_loc_conf 合并配置,并设置删除缓存的handler。
char *
ngx_http_cache_purge_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_cache_purge_loc_conf_t *prev = parent;
ngx_http_cache_purge_loc_conf_t *conf = child;
ngx_http_core_loc_conf_t *clcf;
......
# if (NGX_HTTP_PROXY)
ngx_http_proxy_loc_conf_t *plcf;
# endif /* NGX_HTTP_PROXY */
......
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
# if (NGX_HTTP_PROXY)
ngx_http_cache_purge_merge_conf(&conf->proxy, &prev->proxy);
// clcf->handler != NULL 是因为指向了proxy_pass 的handler
// 也就是说proxy_cache_purge on|off|<method> [from all|<ip> [.. <ip>]] 和proxy_pass 是用的相同的location。
if (conf->proxy.enable && clcf->handler != NULL) {
plcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_proxy_module);
if (plcf->upstream.upstream || plcf->proxy_lengths) {
conf->conf = &conf->proxy;
conf->handler = plcf->upstream.cache
? ngx_http_proxy_cache_purge_handler : NULL;
conf->original_handler = clcf->handler; // 保存了proxy_pass的handler
// 请求执行到content阶段的回调函数,该函数通过判断method来检查是否要删除
clcf->handler = ngx_http_cache_purge_access_handler;
return NGX_CONF_OK;
}
}
# endif /* NGX_HTTP_PROXY */
ngx_conf_merge_ptr_value(conf->conf, prev->conf, NULL);
if (conf->handler == NULL) {
conf->handler = prev->handler;
}
if (conf->original_handler == NULL) {
conf->original_handler = prev->original_handler;
}
return NGX_CONF_OK;
}
content 阶段的回调函数
ngx_int_t
ngx_http_cache_purge_access_handler(ngx_http_request_t *r)
{
ngx_http_cache_purge_loc_conf_t *cplcf;
cplcf = ngx_http_get_module_loc_conf(r, ngx_http_cache_purge_module);
if (r->method_name.len != cplcf->conf->method.len
|| (ngx_strncmp(r->method_name.data, cplcf->conf->method.data,
r->method_name.len)))
{
// 不是删除缓存的请求,则调用proxy pass的回掉函数
return cplcf->original_handler(r);
}
// 是否是删除请求的允许的ip
if ((cplcf->conf->access || cplcf->conf->access6)
&& ngx_http_cache_purge_access(cplcf->conf->access,
cplcf->conf->access6,
r->connection->sockaddr) != NGX_OK)
{
return NGX_HTTP_FORBIDDEN;
}
if (cplcf->handler == NULL) {
return NGX_HTTP_NOT_FOUND;
}
return cplcf->handler(r);
}
ngx_http_proxy_cache_purge_handler 真正执行删除缓存文件和节点的函数
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;
# if (nginx_version >= 1007009)
ngx_http_proxy_main_conf_t *pmcf;
ngx_int_t rc;
# endif /* nginx_version >= 1007009 */
if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
r->upstream->conf = &plcf->upstream;
# if (nginx_version >= 1007009)
pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module);
r->upstream->caches = &pmcf->caches;
rc = ngx_http_cache_purge_cache_get(r, r->upstream, &cache);
if (rc != NGX_OK) {
return rc;
}
# else
cache = plcf->upstream.cache->data;
# endif /* nginx_version >= 1007009 */
if (ngx_http_cache_purge_init(r, cache, &plcf->cache_key) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
# if (nginx_version >= 8011)
r->main->count++;
# endif
ngx_http_cache_purge_handler(r);
return NGX_DONE;
}
void
ngx_http_cache_purge_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
# if (NGX_HAVE_FILE_AIO)
if (r->aio) {
return;
}
# endif
rc = ngx_http_file_cache_purge(r);
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:
r->write_event_handler = ngx_http_request_empty_handler;
ngx_http_finalize_request(r, ngx_http_cache_purge_send_response(r));
return;
case NGX_DECLINED:
ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
return;
# if (NGX_HAVE_FILE_AIO)
case NGX_AGAIN:
r->write_event_handler = ngx_http_cache_purge_handler;
return;
# endif
default:
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
}
ngx_int_t
ngx_http_file_cache_purge(ngx_http_request_t *r)
{
ngx_http_file_cache_t *cache;
ngx_http_cache_t *c;
switch (ngx_http_file_cache_open(r)) {
case NGX_OK:
case NGX_HTTP_CACHE_STALE:
# if (nginx_version >= 8001) \
|| ((nginx_version < 8000) && (nginx_version >= 7060))
case NGX_HTTP_CACHE_UPDATING:
# endif
break;
case NGX_DECLINED:
return NGX_DECLINED;
# if (NGX_HAVE_FILE_AIO)
case NGX_AGAIN:
return NGX_AGAIN;
# endif
default:
return NGX_ERROR;
}
c = r->cache;
cache = c->file_cache;
/*
* 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;
}
# if (nginx_version >= 1000001)
cache->sh->size -= c->node->fs_size;
c->node->fs_size = 0;
# else
cache->sh->size -= (c->node->length + cache->bsize - 1) / cache->bsize;
c->node->length = 0;
# endif
c->node->exists = 0;
# if (nginx_version >= 8001) \
|| ((nginx_version < 8000) && (nginx_version >= 7060))
c->node->updating = 0;
# endif
ngx_shmtx_unlock(&cache->shpool->mutex);
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, r->connection->log, ngx_errno,
ngx_delete_file_n " \"%s\" failed", c->file.name.data);
}
/* file deleted from cache */
return NGX_OK;
}