/* * create a new URL entity in the cache * * It is possible to store more than once entity per URL. This * function will always create a new entity, regardless of whether * other entities already exist for the same URL. * * The size of the entity is provided so that a cache module can * decide whether or not it wants to cache this particular entity. * If the size is unknown, a size of -1 should be set. */ int cache_create_entity(request_rec *r, char *url, apr_off_t size) { cache_provider_list *list; cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t)); char *key; apr_status_t rv; cache_request_rec *cache = (cache_request_rec *) ap_get_module_config(r->request_config, &cache_module); rv = cache_generate_key(r,r->pool,&key); if (rv != APR_SUCCESS) { return rv; } list = cache->providers; /* for each specified cache type, delete the URL */ while (list) { switch (rv = list->provider->create_entity(h, r, key, size)) { case OK: { cache->handle = h; cache->provider = list->provider; cache->provider_name = list->provider_name; return OK; } case DECLINED: { list = list->next; continue; } default: { return rv; } } } return DECLINED; }
/** * Remove the cache lock, if present. * * First, try to close the file handle, whose delete-on-close should * kill the file. Otherwise, just delete the file by name. * * If no lock name has yet been calculated, do the calculation of the * lock name first before trying to delete the file. * * If an optional bucket brigade is passed, the lock will only be * removed if the bucket brigade contains an EOS bucket. */ apr_status_t cache_remove_lock(cache_server_conf *conf, cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb) { void *dummy; const char *lockname; if (!conf || !conf->lock || !conf->lockpath) { /* no locks configured, leave */ return APR_SUCCESS; } if (bb) { apr_bucket *e; int eos_found = 0; for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) { if (APR_BUCKET_IS_EOS(e)) { eos_found = 1; break; } } if (!eos_found) { /* no eos found in brigade, don't delete anything just yet, * we are not done. */ return APR_SUCCESS; } } apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); if (dummy) { return apr_file_close((apr_file_t *)dummy); } apr_pool_userdata_get(&dummy, CACHE_LOCKNAME_KEY, r->pool); lockname = (const char *)dummy; if (!lockname) { char dir[5]; /* create the key if it doesn't exist */ if (!cache->key) { cache_generate_key(r, r->pool, &cache->key); } /* create a hashed filename from the key, and save it for later */ lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key); /* lock files represent discrete just-went-stale URLs "in flight", so * we support a simple two level directory structure, more is overkill. */ dir[0] = '/'; dir[1] = lockname[0]; dir[2] = '/'; dir[3] = lockname[1]; dir[4] = 0; lockname = apr_pstrcat(r->pool, conf->lockpath, dir, "/", lockname, NULL); } return apr_file_remove(lockname, r->pool); }
/* * create a new URL entity in the cache * * It is possible to store more than once entity per URL. This * function will always create a new entity, regardless of whether * other entities already exist for the same URL. * * The size of the entity is provided so that a cache module can * decide whether or not it wants to cache this particular entity. * If the size is unknown, a size of -1 should be set. */ int cache_create_entity(cache_request_rec *cache, request_rec *r, apr_off_t size, apr_bucket_brigade *in) { cache_provider_list *list; cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t)); apr_status_t rv; if (!cache) { /* This should never happen */ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00692) "cache: No cache request information available for key" " generation"); return APR_EGENERAL; } if (!cache->key) { rv = cache_generate_key(r, r->pool, &cache->key); if (rv != APR_SUCCESS) { return rv; } } list = cache->providers; /* for each specified cache type, delete the URL */ while (list) { switch (rv = list->provider->create_entity(h, r, cache->key, size, in)) { case OK: { cache->handle = h; cache->provider = list->provider; cache->provider_name = list->provider_name; return OK; } case DECLINED: { list = list->next; continue; } default: { return rv; } } } return DECLINED; }
/* * delete all URL entities from the cache * */ int cache_remove_url(request_rec *r, char *url) { cache_provider_list *list; apr_status_t rv; char *key; cache_request_rec *cache = (cache_request_rec *) ap_get_module_config(r->request_config, &cache_module); rv = cache_generate_key(r,r->pool,&key); if (rv != APR_SUCCESS) { return rv; } list = cache->providers; /* for each specified cache type, delete the URL */ while(list) { list->provider->remove_url(key); list = list->next; } return OK; }
/* * select a specific URL entity in the cache * * It is possible to store more than one entity per URL. Content * negotiation is used to select an entity. Once an entity is * selected, details of it are stored in the per request * config to save time when serving the request later. * * This function returns OK if successful, DECLINED if no * cached entity fits the bill. */ int cache_select_url(request_rec *r, char *url) { cache_provider_list *list; apr_status_t rv; cache_handle_t *h; char *key; cache_request_rec *cache = (cache_request_rec *) ap_get_module_config(r->request_config, &cache_module); rv = cache_generate_key(r, r->pool, &key); if (rv != APR_SUCCESS) { return rv; } /* go through the cache types till we get a match */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); list = cache->providers; while (list) { switch ((rv = list->provider->open_entity(h, r, key))) { case OK: { char *vary = NULL; const char *varyhdr = NULL; int fresh; if (list->provider->recall_headers(h, r) != APR_SUCCESS) { /* TODO: Handle this error */ return DECLINED; } /* * Check Content-Negotiation - Vary * * At this point we need to make sure that the object we found in * the cache is the same object that would be delivered to the * client, when the effects of content negotiation are taken into * effect. * * In plain english, we want to make sure that a language-negotiated * document in one language is not given to a client asking for a * language negotiated document in a different language by mistake. * * This code makes the assumption that the storage manager will * cache the req_hdrs if the response contains a Vary * header. * * RFC2616 13.6 and 14.44 describe the Vary mechanism. */ if ((varyhdr = apr_table_get(h->resp_err_hdrs, "Vary")) == NULL) { varyhdr = apr_table_get(h->resp_hdrs, "Vary"); } vary = apr_pstrdup(r->pool, varyhdr); while (vary && *vary) { char *name = vary; const char *h1, *h2; /* isolate header name */ while (*vary && !apr_isspace(*vary) && (*vary != ',')) ++vary; while (*vary && (apr_isspace(*vary) || (*vary == ','))) { *vary = '\0'; ++vary; } /* * is this header in the request and the header in the cached * request identical? If not, we give up and do a straight get */ h1 = apr_table_get(r->headers_in, name); h2 = apr_table_get(h->req_hdrs, name); if (h1 == h2) { /* both headers NULL, so a match - do nothing */ } else if (h1 && h2 && !strcmp(h1, h2)) { /* both headers exist and are equal - do nothing */ } else { /* headers do not match, so Vary failed */ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, "cache_select_url(): Vary header mismatch."); return DECLINED; } } cache->provider = list->provider; cache->provider_name = list->provider_name; /* Is our cached response fresh enough? */ fresh = ap_cache_check_freshness(h, r); if (!fresh) { cache_info *info = &(h->cache_obj->info); /* Make response into a conditional */ /* FIXME: What if the request is already conditional? */ if (info && info->etag) { /* if we have a cached etag */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); apr_table_set(r->headers_in, "If-None-Match", info->etag); cache->stale_handle = h; } else if (info && info->lastmods) { /* if we have a cached Last-Modified header */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); apr_table_set(r->headers_in, "If-Modified-Since", info->lastmods); cache->stale_handle = h; } return DECLINED; } /* Okay, this response looks okay. Merge in our stuff and go. */ apr_table_setn(r->headers_out, "Content-Type", ap_make_content_type(r, h->content_type)); r->filename = apr_pstrdup(r->pool, h->cache_obj->info.filename); accept_headers(h, r); cache->handle = h; return OK; } case DECLINED: { /* try again with next cache type */ list = list->next; continue; } default: { /* oo-er! an error */ return rv; } } } return DECLINED; }
/* * Invalidate a specific URL entity in all caches * * All cached entities for this URL are removed, usually in * response to a POST/PUT or DELETE. * * This function returns OK if at least one entity was found and * removed, and DECLINED if no cached entities were removed. */ int cache_invalidate(cache_request_rec *cache, request_rec *r) { cache_provider_list *list; apr_status_t rv, status = DECLINED; cache_handle_t *h; apr_uri_t location_uri; apr_uri_t content_location_uri; const char *location, *location_key = NULL; const char *content_location, *content_location_key = NULL; if (!cache) { /* This should never happen */ ap_log_rerror( APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key" " generation"); return DECLINED; } if (!cache->key) { rv = cache_generate_key(r, r->pool, &cache->key); if (rv != APR_SUCCESS) { return DECLINED; } } location = apr_table_get(r->headers_out, "Location"); if (location) { if (APR_SUCCESS != apr_uri_parse(r->pool, location, &location_uri) || APR_SUCCESS != cache_canonicalise_key(r, r->pool, location, &location_uri, &location_key) || strcmp(r->parsed_uri.hostname, location_uri.hostname)) { location_key = NULL; } } content_location = apr_table_get(r->headers_out, "Content-Location"); if (content_location) { if (APR_SUCCESS != apr_uri_parse(r->pool, content_location, &content_location_uri) || APR_SUCCESS != cache_canonicalise_key(r, r->pool, content_location, &content_location_uri, &content_location_key) || strcmp(r->parsed_uri.hostname, content_location_uri.hostname)) { content_location_key = NULL; } } /* go through the cache types */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); list = cache->providers; while (list) { /* invalidate the request uri */ rv = list->provider->open_entity(h, r, cache->key); if (OK == rv) { rv = list->provider->invalidate_entity(h, r); status = OK; } ap_log_rerror( APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key); /* invalidate the Location */ if (location_key) { rv = list->provider->open_entity(h, r, location_key); if (OK == rv) { rv = list->provider->invalidate_entity(h, r); status = OK; } ap_log_rerror( APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key); } /* invalidate the Content-Location */ if (content_location_key) { rv = list->provider->open_entity(h, r, content_location_key); if (OK == rv) { rv = list->provider->invalidate_entity(h, r); status = OK; } ap_log_rerror( APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key); } list = list->next; } return status; }
/* * select a specific URL entity in the cache * * It is possible to store more than one entity per URL. Content * negotiation is used to select an entity. Once an entity is * selected, details of it are stored in the per request * config to save time when serving the request later. * * This function returns OK if successful, DECLINED if no * cached entity fits the bill. */ int cache_select(cache_request_rec *cache, request_rec *r) { cache_provider_list *list; apr_status_t rv; cache_handle_t *h; if (!cache) { /* This should never happen */ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693) "cache: No cache request information available for key" " generation"); return DECLINED; } /* if no-cache, we can't serve from the cache, but we may store to the * cache. */ if (!ap_cache_check_no_cache(cache, r)) { return DECLINED; } if (!cache->key) { rv = cache_generate_key(r, r->pool, &cache->key); if (rv != APR_SUCCESS) { return DECLINED; } } /* go through the cache types till we get a match */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); list = cache->providers; while (list) { switch ((rv = list->provider->open_entity(h, r, cache->key))) { case OK: { char *vary = NULL; int mismatch = 0; char *last = NULL; if (list->provider->recall_headers(h, r) != APR_SUCCESS) { /* try again with next cache type */ list = list->next; continue; } /* * Check Content-Negotiation - Vary * * At this point we need to make sure that the object we found in * the cache is the same object that would be delivered to the * client, when the effects of content negotiation are taken into * effect. * * In plain english, we want to make sure that a language-negotiated * document in one language is not given to a client asking for a * language negotiated document in a different language by mistake. * * This code makes the assumption that the storage manager will * cache the req_hdrs if the response contains a Vary * header. * * RFC2616 13.6 and 14.44 describe the Vary mechanism. */ vary = cache_strqtok( apr_pstrdup(r->pool, cache_table_getm(r->pool, h->resp_hdrs, "Vary")), CACHE_SEPARATOR, &last); while (vary) { const char *h1, *h2; /* * is this header in the request and the header in the cached * request identical? If not, we give up and do a straight get */ h1 = cache_table_getm(r->pool, r->headers_in, vary); h2 = cache_table_getm(r->pool, h->req_hdrs, vary); if (h1 == h2) { /* both headers NULL, so a match - do nothing */ } else if (h1 && h2 && !strcmp(h1, h2)) { /* both headers exist and are equal - do nothing */ } else { /* headers do not match, so Vary failed */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00694) "cache_select(): Vary header mismatch."); mismatch = 1; break; } vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last); } /* no vary match, try next provider */ if (mismatch) { /* try again with next cache type */ list = list->next; continue; } cache->provider = list->provider; cache->provider_name = list->provider_name; /* * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request * that includes both a Last-Modified date and one or more entity tags as * cache validators, MUST NOT return a locally cached response to the * client unless that cached response is consistent with all of the * conditional header fields in the request. */ if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH || ap_condition_if_unmodified_since(r, h->resp_hdrs) == AP_CONDITION_NOMATCH || ap_condition_if_none_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH || ap_condition_if_modified_since(r, h->resp_hdrs) == AP_CONDITION_NOMATCH || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) { mismatch = 1; } /* Is our cached response fresh enough? */ if (mismatch || !cache_check_freshness(h, cache, r)) { const char *etag, *lastmod; /* Cache-Control: only-if-cached and revalidation required, try * the next provider */ if (cache->control_in.only_if_cached) { /* try again with next cache type */ list = list->next; continue; } /* set aside the stale entry for accessing later */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); cache->stale_handle = h; /* if no existing conditionals, use conditionals of our own */ if (!mismatch) { ap_log_rerror( APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding " "conditional request headers.", r->uri); /* Remove existing conditionals that might conflict with ours */ apr_table_unset(r->headers_in, "If-Match"); apr_table_unset(r->headers_in, "If-Modified-Since"); apr_table_unset(r->headers_in, "If-None-Match"); apr_table_unset(r->headers_in, "If-Range"); apr_table_unset(r->headers_in, "If-Unmodified-Since"); etag = apr_table_get(h->resp_hdrs, "ETag"); lastmod = apr_table_get(h->resp_hdrs, "Last-Modified"); if (etag || lastmod) { /* If we have a cached etag and/or Last-Modified add in * our own conditionals. */ if (etag) { apr_table_set(r->headers_in, "If-None-Match", etag); } if (lastmod) { apr_table_set(r->headers_in, "If-Modified-Since", lastmod); } /* * Do not do Range requests with our own conditionals: If * we get 304 the Range does not matter and otherwise the * entity changed and we want to have the complete entity */ apr_table_unset(r->headers_in, "Range"); } } /* ready to revalidate, pretend we were never here */ return DECLINED; } /* Okay, this response looks okay. Merge in our stuff and go. */ cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 0); cache->handle = h; return OK; } case DECLINED: { /* try again with next cache type */ list = list->next; continue; } default: { /* oo-er! an error */ return rv; } } } /* if Cache-Control: only-if-cached, and not cached, return 504 */ if (cache->control_in.only_if_cached) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696) "cache: 'only-if-cached' requested and no cached entity, " "returning 504 Gateway Timeout for: %s", r->uri); return HTTP_GATEWAY_TIME_OUT; } return DECLINED; }
/** * Try obtain a cache wide lock on the given cache key. * * If we return APR_SUCCESS, we obtained the lock, and we are clear to * proceed to the backend. If we return APR_EEXIST, then the lock is * already locked, someone else has gone to refresh the backend data * already, so we must return stale data with a warning in the mean * time. If we return anything else, then something has gone pear * shaped, and we allow the request through to the backend regardless. * * This lock is created from the request pool, meaning that should * something go wrong and the lock isn't deleted on return of the * request headers from the backend for whatever reason, at worst the * lock will be cleaned up when the request dies or finishes. * * If something goes truly bananas and the lock isn't deleted when the * request dies, the lock will be trashed when its max-age is reached, * or when a request arrives containing a Cache-Control: no-cache. At * no point is it possible for this lock to permanently deny access to * the backend. */ apr_status_t cache_try_lock(cache_server_conf *conf, cache_request_rec *cache, request_rec *r) { apr_status_t status; const char *lockname; const char *path; char dir[5]; apr_time_t now = apr_time_now(); apr_finfo_t finfo; apr_file_t *lockfile; void *dummy; finfo.mtime = 0; if (!conf || !conf->lock || !conf->lockpath) { /* no locks configured, leave */ return APR_SUCCESS; } /* lock already obtained earlier? if so, success */ apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); if (dummy) { return APR_SUCCESS; } /* create the key if it doesn't exist */ if (!cache->key) { cache_generate_key(r, r->pool, &cache->key); } /* create a hashed filename from the key, and save it for later */ lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key); /* lock files represent discrete just-went-stale URLs "in flight", so * we support a simple two level directory structure, more is overkill. */ dir[0] = '/'; dir[1] = lockname[0]; dir[2] = '/'; dir[3] = lockname[1]; dir[4] = 0; /* make the directories */ path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL); if (APR_SUCCESS != (status = apr_dir_make_recursive(path, APR_UREAD|APR_UWRITE|APR_UEXECUTE, r->pool))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00778) "Could not create a cache lock directory: %s", path); return status; } lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL); apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool); /* is an existing lock file too old? */ status = apr_stat(&finfo, lockname, APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool); if (!(APR_STATUS_IS_ENOENT(status)) && APR_SUCCESS != status) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EEXIST, r, APLOGNO(00779) "Could not stat a cache lock file: %s", lockname); return status; } if ((status == APR_SUCCESS) && (((now - finfo.mtime) > conf->lockmaxage) || (now < finfo.mtime))) { ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO(00780) "Cache lock file for '%s' too old, removing: %s", r->uri, lockname); apr_file_remove(lockname, r->pool); } /* try obtain a lock on the file */ if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname, APR_WRITE | APR_CREATE | APR_EXCL | APR_DELONCLOSE, APR_UREAD | APR_UWRITE, r->pool))) { apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool); } return status; }