mod_cache.c revision 5ce997c7890c4bda0bd7551fdf264a880f5feeea
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mod_cache.h"
#include "cache_storage.h"
#include "cache_util.h"
/* -------------------------------------------------------------- */
/* Handles for cache filters, resolved at startup to eliminate
* a name-to-function mapping on each request
*/
static ap_filter_rec_t *cache_filter_handle;
static ap_filter_rec_t *cache_save_filter_handle;
static ap_filter_rec_t *cache_out_filter_handle;
/*
* CACHE handler
* -------------
*
* Can we deliver this request from the cache?
* If yes:
* deliver the content by installing the CACHE_OUT filter.
* If no:
* check whether we're allowed to try cache it
* If yes:
* add CACHE_SAVE filter
* If No:
* oh well.
*
* By default, the cache handler runs in the quick handler, bypassing
* virtually all server processing and offering the cache its optimal
* performance. In this mode, the cache bolts onto the front of the
* server, and behaves as a discrete RFC2616 caching proxy
* implementation.
*
* Under certain circumstances, an admin might want to run the cache as
* a normal handler instead of a quick handler, allowing the cache to
* run after the authorisation hooks, or by allowing fine control over
* the placement of the cache in the filter chain. This option comes at
* a performance penalty, and should only be used to achieve specific
* caching goals where the admin understands what they are doing.
*/
{
const char *auth;
apr_bucket *e;
&cache_module);
/* only run if the quick handler is enabled */
return DECLINED;
}
/*
* Which cache module (if any) should handle this request?
*/
return DECLINED;
}
/* make space for the per request config */
/* save away the possible providers */
/*
* Are we allowed to serve cached info at all?
*/
/* find certain cache controlling headers */
/* First things first - does the request allow us to return
* cached information at all? If not, just decline the request.
*/
if (auth) {
return DECLINED;
}
/* Are we something other than GET or HEAD? If so, invalidate
* the cached entities.
*/
if (r->method_number != M_GET) {
"Invalidating all cached entities in response to '%s' request for %s",
cache_invalidate(cache, r);
/* we've got a cache invalidate! tell everyone who cares */
"cache invalidated by %s", r->method));
return DECLINED;
}
/*
* Try to serve this request from the cache.
*
* If no existing cache file (DECLINED)
* add cache_save filter
* If cached file (OK)
* clear filter stack
* add cache_out filter
* return OK
*/
if (!lookup) {
/* try to obtain a cache lock at this point. if we succeed,
* we are the first to try and cache this url. if we fail,
* it means someone else is already trying to cache this
* url, and we should just let the request through to the
* backend without any attempt to cache. this stops
* duplicated simultaneous attempts to cache an entity.
*/
if (APR_SUCCESS == rv) {
/*
* Add cache_save filter to cache this request. Choose
* the correct filter by checking if we are a subrequest
* or not.
*/
if (r->main) {
r->uri);
r->connection);
}
else {
r->uri);
r->connection);
}
"Adding CACHE_REMOVE_URL filter for %s",
r->uri);
/* Add cache_remove_url filter to this request to remove a
* stale cache entry if needed. Also put the current cache
* request rec in the filter context, as the request that
* is available later during running the filter may be
* different due to an internal redirect.
*/
r->connection);
}
else {
"response: %s", r->uri);
}
}
else {
if (cache->stale_headers) {
r->uri);
}
}
}
else {
/* error */
return rv;
}
return DECLINED;
}
/* we've got a cache hit! tell everyone who cares */
"cache hit");
/* if we are a lookup, we are exiting soon one way or another; Restore
* the headers. */
if (lookup) {
if (cache->stale_headers) {
"Restoring request headers.");
}
}
rv = ap_meets_conditions(r);
/* If we are a lookup, we have to return DECLINED as we have no
* way of knowing if we will be able to serve the content.
*/
if (lookup) {
return DECLINED;
}
/* Return cached status. */
return rv;
}
/* If we're a lookup, we can exit now instead of serving the content. */
if (lookup) {
return OK;
}
/* Serve up the content */
/* We are in the quick handler hook, which means that no output
* filters have been set. So lets run the insert_filter hook.
*/
/*
* Add cache_out filter to serve this request. Choose
* the correct filter by checking if we are a subrequest
* or not.
*/
if (r->main) {
}
else {
}
/*
* Remove all filters that are before the cache_out filter. This ensures
* that we kick off the filter stack with our cache_out filter being the
* first in the chain. This make sense because we want to restore things
* in the same manner as we saved them.
* There may be filters before our cache_out filter, because
*
* 1. We call ap_set_content_type during cache_select. This causes
* Content-Type specific filters to be added.
* 2. We call the insert_filter hook. This causes filters e.g. like
* the ones set with SetOutputFilter to be added.
*/
next = r->output_filters;
}
/* kick off the filter stack */
return ap_pass_brigade_fchk(r, out,
"cache_quick_handler(%s): ap_pass_brigade returned",
}
/**
* If the two filter handles are present within the filter chain, replace
* the last instance of the first filter with the last instance of the
* second filter, and return true. If the second filter is not present at
* all, the first filter is removed, and false is returned. If neither
* filter is present, false is returned and this function does nothing.
* If a stop filter is specified, processing will stop once this filter is
* reached.
*/
}
}
}
return 1;
}
if (ffrom) {
}
return 0;
}
/**
* Find the given filter, and return it if found, or NULL otherwise.
*/
while (next) {
break;
}
}
return next;
}
/**
* The cache handler is functionally similar to the cache_quick_hander,
* however a number of steps that are required by the quick handler are
* not required here, as the normal httpd processing has already handled
* these steps.
*/
static int cache_handler(request_rec *r)
{
apr_bucket *e;
&cache_module);
/* only run if the quick handler is disabled */
return DECLINED;
}
/*
* Which cache module (if any) should handle this request?
*/
return DECLINED;
}
/* make space for the per request config */
/* save away the possible providers */
/* Are we something other than GET or HEAD? If so, invalidate
* the cached entities.
*/
if (r->method_number != M_GET) {
"Invalidating all cached entities in response to '%s' request for %s",
cache_invalidate(cache, r);
/* we've got a cache invalidate! tell everyone who cares */
"cache invalidated by %s", r->method));
return DECLINED;
}
/*
* Try to serve this request from the cache.
*
* If no existing cache file (DECLINED)
* add cache_save filter
* If cached file (OK)
* clear filter stack
* add cache_out filter
* return OK
*/
/* try to obtain a cache lock at this point. if we succeed,
* we are the first to try and cache this url. if we fail,
* it means someone else is already trying to cache this
* url, and we should just let the request through to the
* backend without any attempt to cache. this stops
* duplicated simultaneous attempts to cache an entity.
*/
if (APR_SUCCESS == rv) {
/*
* Add cache_save filter to cache this request. Choose
* the correct filter by checking if we are a subrequest
* or not.
*/
if (r->main) {
r->uri);
}
else {
r->uri);
}
r->connection);
/*
* Did the user indicate the precise location of the
* CACHE_SAVE filter by inserting the CACHE filter as a
* marker?
*
* If so, we get cunning and replace CACHE with the
* CACHE_SAVE filter. This has the effect of inserting
* the CACHE_SAVE filter at the precise location where
* the admin wants to cache the content. All filters that
* lie before and after the original location of the CACHE
* filter will remain in place.
*/
if (cache_replace_filter(r->output_filters,
ap_get_input_filter_handle("SUBREQ_CORE"))) {
"filter for %s", r->uri);
}
/* save away the save filter stack */
"Adding CACHE_REMOVE_URL filter for %s",
r->uri);
/* Add cache_remove_url filter to this request to remove a
* stale cache entry if needed. Also put the current cache
* request rec in the filter context, as the request that
* is available later during running the filter may be
* different due to an internal redirect.
*/
r->connection);
}
else {
"response: %s", r->uri);
}
}
else {
/* error */
return rv;
}
return DECLINED;
}
/* we've got a cache hit! tell everyone who cares */
"cache hit");
rv = ap_meets_conditions(r);
return rv;
}
/* Serve up the content */
/*
* Add cache_out filter to serve this request. Choose
* the correct filter by checking if we are a subrequest
* or not.
*/
if (r->main) {
}
else {
}
/*
* Did the user indicate the precise location of the CACHE_OUT filter by
* inserting the CACHE filter as a marker?
*
* If so, we get cunning and replace CACHE with the CACHE_OUT filters.
* This has the effect of inserting the CACHE_OUT filter at the precise
* location where the admin wants to cache the content. All filters that
* lie *after* the original location of the CACHE filter will remain in
* place.
*/
r->uri);
}
/*
* Remove all filters that are before the cache_out filter. This ensures
* that we kick off the filter stack with our cache_out filter being the
* first in the chain. This make sense because we want to restore things
* in the same manner as we saved them.
* There may be filters before our cache_out filter, because
*
* 1. We call ap_set_content_type during cache_select. This causes
* Content-Type specific filters to be added.
* 2. We call the insert_filter hook. This causes filters e.g. like
* the ones set with SetOutputFilter to be added.
*/
next = r->output_filters;
}
/* kick off the filter stack */
}
/*
* CACHE_OUT filter
* ----------------
*
* Deliver cached content (headers and body) up the stack.
*/
{
request_rec *r = f->r;
apr_bucket *e;
if (!cache) {
/* user likely configured CACHE_OUT manually; they should use mod_cache
* configuration to do that */
}
"cache: running CACHE_OUT filter");
/* clean out any previous response up to EOS, if any */
for (e = APR_BRIGADE_FIRST(in);
e != APR_BRIGADE_SENTINEL(in);
e = APR_BUCKET_NEXT(e))
{
if (APR_BUCKET_IS_EOS(e)) {
r->connection->bucket_alloc);
/* restore content type of cached response if available */
/* Needed especially when stale content gets served. */
if (ct) {
ap_set_content_type(r, ct);
}
/* restore status of cached response */
/* recall_headers() was called in cache_select() */
/* This filter is done once it has served up its content */
"cache: serving %s", r->uri);
}
}
return APR_SUCCESS;
}
/*
* Having jumped through all the hoops and decided to cache the
* response, call store_body() for each brigade, handling the
* case where the provider can't swallow the full brigade. In this
* case, we write the brigade we were passed out downstream, and
* loop around to try and cache some more until the in brigade is
* completely empty. As soon as the out brigade contains eos, call
* commit_entity() to finalise the cached element.
*/
{
int rv = APR_SUCCESS;
apr_bucket *e;
/* pass the brigade in into the cache provider, which is then
* expected to move cached buckets to the out brigade, for us
* to pass up the filter stack. repeat until in is empty, or
* we fail.
*/
if (rv != APR_SUCCESS) {
"cache: Cache provider's store_body failed!");
/* give someone else the chance to cache the file */
/* give up trying to cache, just step out the way */
}
/* does the out brigade contain eos? if so, we're done, commit! */
e = APR_BUCKET_NEXT(e))
{
if (APR_BUCKET_IS_EOS(e)) {
break;
}
}
/* conditionally remove the lock as soon as we see the eos bucket */
if (APR_BRIGADE_EMPTY(in)) {
/* cache provider wants more data before passing the brigade
* upstream, oblige the provider by leaving to fetch more.
*/
break;
}
else {
/* oops, no data out, but not all data read in either, be
* safe and stand down to prevent a spin.
*/
"cache: Cache provider's store_body returned an "
"empty brigade, but didn't consume all of the"
"input brigade, standing down to prevent a spin");
/* give someone else the chance to cache the file */
}
}
}
return rv;
}
/*
* CACHE_SAVE filter
* ---------------
*
* Decide whether or not this content should be cached.
* If we decide no it should not:
* remove the filter from the chain
* If we decide yes it should:
* Have we already started saving the response?
* If we have started, pass the data to the storage manager via store_body
* Otherwise:
* Check to see if we *can* save this particular response.
* If we can, call cache_create_entity() and save the headers and body
* Finally, pass the data to the next filter (the network or whatever)
*
* After the various failure cases, the cache lock is proactively removed, so
* that another request is given the opportunity to attempt to cache without
* waiting for a potentially slow client to acknowledge the failure.
*/
{
request_rec *r = f->r;
char *reason;
apr_pool_t *p;
apr_bucket *e;
&cache_module);
/* Setup cache_request_rec */
if (!cache) {
/* user likely configured CACHE_SAVE manually; they should really use
* mod_cache configuration to do that
*/
"CACHE/CACHE_SAVE filter enabled while caching is disabled, ignoring");
}
p = r->pool;
/*
* Pass Data to Cache
* ------------------
* This section passes the brigades into the cache modules, but only
* if the setup section (see below) is complete.
*/
if (cache->block_response) {
/* We've already sent down the response and EOS. So, ignore
* whatever comes now.
*/
return APR_SUCCESS;
}
/* have we already run the cacheability check and set up the
* cached file handle?
*/
if (cache->in_checked) {
}
/*
* Setup Data in Cache
* -------------------
* This section opens the cache entity and sets various caching
* parameters, and decides whether this URL should be cached at
* all. This section is* run before the above section.
*/
/* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
* If a cache receives a 5xx response while attempting to revalidate an
* entry, it MAY either forward this response to the requesting client,
* or act as if the server failed to respond. In the latter case, it MAY
* return a previously received response unless the cached entry
* includes the "must-revalidate" cache-control directive (see section
* 14.9).
*
* This covers the case where an error was generated behind us, for example
* by a backend server via mod_proxy.
*/
if (cache->stale_handle
const char *warn_head;
/* morph the current save filter into the out filter, and serve from
* cache.
*/
if (r->main) {
}
else {
f->frec = cache_out_filter_handle;
}
/* add a revalidation warning */
"111 Revalidation failed");
}
apr_psprintf(r->pool,
"cache hit: %d status; stale content returned",
r->status));
/* give someone else the chance to cache the file */
/* pass brigade to our morphed out filter */
return ap_pass_brigade(f, in);
}
}
/* read expiry date; if a bad date, then leave it so the client can
* read it
*/
}
}
else {
exp = APR_DATE_BAD;
}
/* read the last-modified date; if the date is bad, then delete it */
}
if (lastmod == APR_DATE_BAD) {
}
}
else {
}
/* read the etag and cache-control from the entity */
}
headers = r->err_headers_out;
if (!cc_out && !pragma) {
headers = r->headers_out;
}
/* Have we received a 304 response without any headers at all? Fall back to
* the original headers in the original cached request.
*/
&& !pragma) {
}
/* Parse the cache control header */
/*
* what responses should we not cache?
*
* At this point we decide based on the response headers whether it
* is appropriate _NOT_ to cache the data from the server. There are
* a whole lot of conditions that prevent us from caching this data.
* They are tested here one by one to be clear and unambiguous.
*/
&& r->status != HTTP_PARTIAL_CONTENT
&& r->status != HTTP_MULTIPLE_CHOICES
&& r->status != HTTP_MOVED_PERMANENTLY
&& r->status != HTTP_NOT_MODIFIED) {
/* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410
* We allow the caching of 206, but a cache implementation might choose
* to decline to cache a 206 if it doesn't know how to.
* We include 304 Not Modified here too as this is the origin server
* telling us to serve the cached copy.
*/
/* We are also allowed to cache any response given that it has a
* valid Expires or Cache Control header. If we find a either of
* those here, we pass request through the rest of the tests. From
* the RFC:
*
* A response received with any other status code (e.g. status
* codes 302 and 307) MUST NOT be returned in a reply to a
* subsequent request unless there are cache-control directives or
* another header(s) that explicitly allow it. For example, these
* include the following: an Expires header (section 14.21); a
* "max-age", "s-maxage", "must-revalidate", "proxy-revalidate",
* "public" or "private" cache-control directive (section 14.9).
*/
}
else {
}
}
if (reason) {
/* noop */
}
/* if a broken Expires header is present, don't cache it */
}
&& exp < r->request_time) {
/* if a Expires header is in the past, don't cache it */
reason = "Expires header already expired; not cacheable";
}
/* if we're already stale, but can never revalidate, don't cache it */
= "s-maxage or max-age zero and no Last-Modified or Etag; not cacheable";
}
/* if a query string is present but no explicit expiration time,
* don't cache it (RFC 2616/13.9 & 13.2.1)
*/
reason = "Query string present but no explicit expiration time";
}
else if (r->status == HTTP_NOT_MODIFIED &&
/* if the server said 304 Not Modified but we have no cache
* file - pass this untouched to the user agent, it's not for us.
*/
reason = "HTTP Status 304 Not Modified";
}
/* 200 OK response from HTTP/1.0 and up without Last-Modified,
* Etag, Expires, Cache-Control:max-age, or Cache-Control:s-maxage
* headers.
*/
/* Note: mod-include clears last_modified/expires/etags - this
* is why we have an optional function for a key-gen ;-)
*/
reason = "No Last-Modified; Etag; Expires; Cache-Control:max-age or Cache-Control:s-maxage headers";
}
/* RFC2616 14.9.2 Cache-Control: no-store response
* indicating do not cache, or stop now if you are
* trying to cache it.
*/
reason = "Cache-Control: no-store present";
}
/* RFC2616 14.9.1 Cache-Control: private response
* this object is marked for this user's eyes only. Behave
* as a tunnel.
*/
reason = "Cache-Control: private present";
}
/* RFC2616 14.8 Authorisation:
* if authorisation is included in the request, we don't cache,
* but we can cache if the following exceptions are true:
* 1) If Cache-Control: s-maxage is included
* 2) If Cache-Control: must-revalidate is included
* 3) If Cache-Control: public is included
*/
reason = "Authorization required";
}
reason = "Vary header contains '*'";
}
reason = "environment variable 'no-cache' is set";
}
else if (r->no_cache) {
/* or we've been asked not to cache it above */
reason = "r->no_cache present";
}
/* Hold the phone. Some servers might allow us to cache a 2xx, but
* then make their 304 responses non cacheable. This leaves us in a
* sticky position. If the 304 is in answer to our own conditional
* request, we cannot send this 304 back to the client because the
* client isn't expecting it. Instead, our only option is to respect
* the answer to the question we asked (has it changed, answer was
* no) and return the cached item to the client, and then respect
* the uncacheable nature of this 304 by allowing the remove_url
* filter to kick in and remove the cached entity.
*/
cache->stale_handle) {
int status;
/* Load in the saved status and clear the status line. */
r->status_line = NULL;
status = ap_meets_conditions(r);
}
else {
/* RFC 2616 10.3.5 states that entity headers are not supposed
* to be in the 304 response. Therefore, we need to combine the
* response headers with the cached headers *before* we update
* the cached headers.
*
* However, before doing that, we need to first merge in
* err_headers_out and we also need to strip any hop-by-hop
* headers that might have snuck in.
*/
/* Merge in our cached headers. However, keep any updated values. */
}
/* we've got a cache conditional hit! tell anyone who cares */
r,
r->headers_out,
r->pool,
"conditional cache hit: 304 was uncacheable though (%s); entity removed",
reason));
/* let someone else attempt to cache */
}
if (reason) {
"cache: %s not cached. Reason: %s", r->unparsed_uri,
reason);
/* we've got a cache miss! tell anyone who cares */
reason);
/* remove this filter from the chain */
/* remove the lock file unconditionally */
/* ship the data up the stack */
}
/* Make it so that we don't execute this path again. */
/* Set the content length if known.
*/
}
if (cl) {
char *errp;
}
}
if (!cl) {
/* if we don't get the content-length, see if we have all the
* buckets and use their length to calculate the size
*/
int all_buckets_here=0;
size=0;
for (e = APR_BRIGADE_FIRST(in);
e != APR_BRIGADE_SENTINEL(in);
e = APR_BUCKET_NEXT(e))
{
if (APR_BUCKET_IS_EOS(e)) {
break;
}
if (APR_BUCKET_IS_FLUSH(e)) {
continue;
}
break;
}
}
if (!all_buckets_here) {
size = -1;
}
}
/* remember content length to check response size against later */
/* It's safe to cache the response.
*
* There are two possiblities at this point:
* - cache->handle == NULL. In this case there is no previously
* cached entity anywhere on the system. We must create a brand
* new entity and store the response in it.
* - cache->stale_handle != NULL. In this case there is a stale
* entity in the system which needs to be replaced by new
* content (unless the result was 304 Not Modified, which means
* the cached entity is actually fresh, and we should update
* the headers).
*/
/* Did we have a stale cache entry that really is stale?
*/
if (cache->stale_handle) {
if (r->status == HTTP_NOT_MODIFIED) {
/* Oh, hey. It isn't that stale! Yay! */
}
else {
/* Oh, well. Toss it. */
/* Treat the request as if it wasn't conditional. */
/*
* Restore the original request headers as they may be needed
* by further output filters like the byterange filter to make
* the correct decisions.
*/
}
}
/* no cache handle, create a new entity */
/* We only set info->status upon the initial creation. */
}
/* we've got a cache miss! tell anyone who cares */
"cache miss: create_entity failed");
/* Caching layer declined the opportunity to cache the response */
}
"cache: Caching url: %s", r->unparsed_uri);
/* We are actually caching this response. So it does not
* make sense to remove this entity any more.
*/
"cache: Removing CACHE_REMOVE_URL filter.");
/*
* We now want to update the cache file header information with
* the new date, last modified, expire and content length and write
* it away to our cache file. First, we determine these values from
* the response, using heuristics if appropriate.
*
* In addition, we make HTTP/1.1 age calculations and write them away
* too.
*/
/* store away the previously parsed cache control headers */
/* Read the date. Generate one if one is not supplied */
}
}
else {
}
now = apr_time_now();
/* no date header (or bad header)! */
}
/* set response_time for HTTP/1.1 age calculations */
/* get the request time */
/* check last-modified date */
/* if it's in the future, then replace by date */
"replacing with now");
}
/* if no expiry date then
* if Cache-Control: max-age
* expiry date = date + max-age
* else if lastmod
* expiry date = date + min((date - lastmod) * factor, maxexpire)
* else
* expire date = date + defaultexpire
*/
if (exp == APR_DATE_BAD) {
apr_int64_t x;
errno = 0;
x = control.max_age_value;
if (errno) {
}
else {
x = x * MSEC_ONE_SEC;
}
}
}
}
/* if lastmod == date then you get 0*conf->factor which results in
* an expiration time of now. This causes some problems with
* freshness calculations, so we choose the else path...
*/
}
}
}
else {
}
}
/* We found a stale entry which wasn't really stale. */
if (cache->stale_handle) {
/* Load in the saved status and clear the status line. */
r->status_line = NULL;
/* RFC 2616 10.3.5 states that entity headers are not supposed
* to be in the 304 response. Therefore, we need to combine the
* response headers with the cached headers *before* we update
* the cached headers.
*
* However, before doing that, we need to first merge in
* err_headers_out and we also need to strip any hop-by-hop
* headers that might have snuck in.
*/
/* Merge in our cached headers. However, keep any updated values. */
}
/* Write away header information to cache. It is possible that we are
* trying to update headers for an entity which has already been cached.
*
* This may fail, due to an unwritable cache area. E.g. filesystem full,
* permissions problems or a read-only (re)mount. This must be handled
* later.
*/
/* Did we just update the cached headers on a revalidated response?
*
* If so, we can now decide what to serve to the client. This is done in
* the same way as with a regular response, but conditions are now checked
* against the cached or merged response headers.
*/
if (cache->stale_handle) {
int status;
/* We're just saving response headers, so we are done. Commit
* the response at this point, unless there was a previous error.
*/
if (rv == APR_SUCCESS) {
}
/* Restore the original request headers and see if we need to
* return anything else than the cached response (ie. the original
* request was conditional).
*/
status = ap_meets_conditions(r);
}
else {
}
/* Before returning we need to handle the possible case of an
* unwritable cache. Rather than leaving the entity in the cache
* and having it constantly re-validated, now that we have recalled
* the body it is safe to try and remove the url from the cache.
*/
if (rv != APR_SUCCESS) {
"cache: updating headers with store_headers failed. "
"Removing cached url.");
/* Probably a mod_cache_disk cache area has been (re)mounted
* read-only, or that there is a permissions problem.
*/
"cache: attempt to remove url from cache unsuccessful.");
}
/* we've got a cache conditional hit! tell anyone who cares */
"conditional cache hit: entity refresh failed");
}
else {
/* we've got a cache conditional hit! tell anyone who cares */
"conditional cache hit: entity refreshed");
}
/* let someone else attempt to cache */
}
if (rv != APR_SUCCESS) {
"cache: store_headers failed");
/* we've got a cache miss! tell anyone who cares */
"cache miss: store_headers failed");
}
/* we've got a cache miss! tell anyone who cares */
"cache miss: attempting entity save");
}
/*
* CACHE_REMOVE_URL filter
* -----------------------
*
* This filter gets added in the quick handler every time the CACHE_SAVE filter
* gets inserted. Its purpose is to remove a confirmed stale cache entry from
* the cache.
*
* CACHE_REMOVE_URL has to be a protocol filter to ensure that is run even if
* the response is a canned error message, which removes the content filters
* and thus the CACHE_SAVE filter from the chain.
*
* CACHE_REMOVE_URL expects cache request rec within its context because the
* request this filter runs on can be different from the one whose cache entry
* should be removed, due to internal redirects.
*
* Note that CACHE_SAVE_URL (as a content-set filter, hence run before the
* protocol filters) will remove this filter if it decides to cache the file.
* Therefore, if this filter is left in, it must mean we need to toss any
* existing files.
*/
{
request_rec *r = f->r;
/* Setup cache_request_rec */
if (!cache) {
/* user likely configured CACHE_REMOVE_URL manually; they should really
* use mod_cache configuration to do that. So:
* 1. Remove ourselves
* 2. Do nothing and bail out
*/
"cache: CACHE_REMOVE_URL enabled unexpectedly");
}
/* Now remove this cache entry from the cache */
cache_remove_url(cache, r);
/* remove ourselves */
}
/*
* CACHE filter
* ------------
*
* This filter can be optionally inserted into the filter chain by the admin as
* a marker representing the precise location within the filter chain where
* caching is to be performed.
*
* When the filter chain is set up in the non-quick version of the URL handler,
* the CACHE filter is replaced by the CACHE_OUT or CACHE_SAVE filter,
* effectively inserting the caching filters at the point indicated by the
* admin. The CACHE filter is then removed.
*
* This allows caching to be performed before the content is passed to the
* INCLUDES filter, or to a filter that might perform transformations unique
* to the specific request and that would otherwise be non-cacheable.
*/
{
*conf =
&cache_module);
/* was the quick handler enabled */
"cache: CACHE filter was added in quick handler mode and "
"will be ignored: %s", f->r->unparsed_uri);
}
/* otherwise we may have been bypassed, nothing to see here */
else {
"cache: CACHE filter was added twice, or was added where "
"the cache has been bypassed and will be ignored: %s",
f->r->unparsed_uri);
}
/* we are just a marker, so let's just remove ourselves */
}
/**
* If configured, add the status of the caching attempt to the subprocess
* environment, and if configured, to headers in the response.
*
* The status is saved below the broad category of the status (hit, miss,
* revalidate), as well as a single cache-status key. This can be used for
* conditional logging.
*
* The status is optionally saved to an X-Cache header, and the detail of
* why a particular cache entry was cached (or not cached) is optionally
* saved to an X-Cache-Detail header. This extra detail is useful for
* service developers who may need to know whether their Cache-Control headers
* are working correctly.
*/
{
*conf =
&cache_module);
int x_cache = 0, x_cache_detail = 0;
switch (status) {
case AP_CACHE_HIT: {
break;
}
case AP_CACHE_REVALIDATE: {
break;
}
case AP_CACHE_MISS: {
break;
}
case AP_CACHE_INVALIDATE: {
break;
}
}
}
else {
}
if (x_cache) {
r->server->server_hostname));
}
}
else {
}
if (x_cache_detail) {
}
return OK;
}
/**
* If an error has occurred, but we have a stale cached entry, restore the
* filter stack from the save filter onwards. The canned error message will
* be discarded in the process, and replaced with the cached response.
*/
static void cache_insert_error_filter(request_rec *r)
{
void *dummy;
/* ignore everything except for 5xx errors */
if (r->status < HTTP_INTERNAL_SERVER_ERROR) {
return;
}
if (!dconf->stale_on_error) {
return;
}
/* RFC2616 13.8 Errors or Incomplete Response Cache Behavior:
* If a cache receives a 5xx response while attempting to revalidate an
* entry, it MAY either forward this response to the requesting client,
* or act as if the server failed to respond. In the latter case, it MAY
* return a previously received response unless the cached entry
* includes the "must-revalidate" cache-control directive (see section
* 14.9).
*
* This covers the case where the error was generated by our server via
* ap_die().
*/
if (dummy) {
const char *warn_head;
*conf =
&cache_module);
/* morph the current save filter into the out filter, and serve from
* cache.
*/
if (r->main) {
}
else {
}
/* add a revalidation warning */
"111 Revalidation failed");
}
r,
r->err_headers_out,
r->pool,
"cache hit: %d status; stale content returned",
r->status));
/* give someone else the chance to cache the file */
}
}
return;
}
/* -------------------------------------------------------------- */
/* Setup configurable data */
{
dconf->no_last_mod_ignore = 0;
dconf->store_expired = 0;
dconf->store_private = 0;
dconf->store_nostore = 0;
/* maximum time to cache a document */
/* default time to cache a document */
/* factor used to estimate Expires date from LastModified date */
/* array of providers for this URL space */
return dconf;
}
new->no_last_mod_ignore = (add->no_last_mod_ignore_set == 0) ? base->no_last_mod_ignore : add->no_last_mod_ignore;
/* maximum time to cache a document */
/* default time to cache a document */
/* factor used to estimate Expires date from LastModified date */
: add->x_cache_detail;
|| base->x_cache_detail_set;
: add->stale_on_error;
|| base->stale_on_error_set;
return new;
}
{
/* array of URL prefixes for which caching is enabled */
/* array of URL prefixes for which caching is disabled */
ps->ignorecachecontrol = 0;
ps->ignorecachecontrol_set = 0;
/* array of headers that should not be stored in cache */
/* flag indicating that query-string should be ignored when caching */
ps->ignorequerystring = 0;
ps->ignorequerystring_set = 0;
/* by default, run in the quick handler */
/* array of identifiers that should not be used for key calculation */
return ps;
}
{
/* array of URL prefixes for which caching is disabled */
/* array of URL prefixes for which caching is enabled */
(overrides->ignorecachecontrol_set == 0)
ps->ignore_headers =
(overrides->ignorequerystring_set == 0)
(overrides->lockpath_set == 0)
ps->lockmaxage =
(overrides->lockmaxage_set == 0)
? base->lockmaxage
: overrides->lockmaxage;
(overrides->x_cache_set == 0)
ps->x_cache_detail =
(overrides->x_cache_detail_set == 0)
(overrides->base_uri_set == 0)
return ps;
}
int flag)
{
conf =
,
&cache_module);
return NULL;
}
int flag)
{
return NULL;
}
{
conf =
&cache_module);
return NULL;
}
int flag)
{
return NULL;
}
int flag)
{
return NULL;
}
int flag)
{
return NULL;
}
const char *header)
{
char **new;
conf =
&cache_module);
/* if header None is listed clear array */
}
else {
/* Only add header if no "None" has been found in header list
* so far.
* (When 'None' is passed, IGNORE_HEADERS_SET && nelts == 0.)
*/
}
}
return NULL;
}
const char *identifier)
{
char **new;
conf =
&cache_module);
/* if identifier None is listed clear array */
}
else {
/*
* Only add identifier if no "None" has been found in identifier
* list so far.
*/
(*new) = (char *)identifier;
}
}
return NULL;
}
const char *type,
const char *url)
{
struct cache_enable *new;
return err;
}
if (*type == '/') {
"provider (%s) starts with a '/'. Are url and provider switched?",
type);
}
if (!url) {
}
if (!url) {
"CacheEnable provider (%s) is missing an URL.", type);
}
return "When in a Location, CacheEnable must specify a path or an URL below "
"that location.";
}
conf =
&cache_module);
}
else {
}
return NULL;
}
} else {
}
return NULL;
}
const char *url)
{
struct cache_disable *new;
return err;
}
conf =
&cache_module);
return NULL;
}
else {
return "CacheDisable must be followed by the word 'on' when in a Location.";
}
}
return "CacheDisable must specify a path or an URL.";
}
return NULL;
}
} else {
}
return NULL;
}
const char *arg)
{
return NULL;
}
const char *arg)
{
return NULL;
}
const char *arg)
{
return NULL;
}
const char *arg)
{
double val;
return "CacheLastModifiedFactor value must be a float";
}
return NULL;
}
int flag)
{
conf =
&cache_module);
return NULL;
}
int flag)
{
conf =
&cache_module);
return NULL;
}
const char *arg)
{
conf =
&cache_module);
}
return NULL;
}
const char *arg)
{
conf =
&cache_module);
if (seconds <= 0) {
return "CacheLockMaxAge value must be a non-zero positive integer";
}
return NULL;
}
{
}
else {
&cache_module);
}
return NULL;
}
{
}
else {
&cache_module);
}
return NULL;
}
const char *arg)
{
conf =
&cache_module);
if (rv != APR_SUCCESS) {
}
return apr_psprintf(parms->pool, "URL '%s' must contain at least one of a scheme, a hostname or a port.", arg);
}
return NULL;
}
int flag)
{
return NULL;
}
{
/* This is the means by which unusual (non-unix) os's may find alternate
*/
if (!cache_generate_key) {
}
return OK;
}
static const command_rec cache_cmds[] =
{
/* XXX
* Consider a new config directive that enables loading specific cache
* implememtations (like mod_cache_mem, mod_cache_file, etc.).
* Rather than using a LoadModule directive, admin would use something
* like CacheModule mem_cache_module | file_cache_module, etc,
* which would cause the approprpriate cache module to be loaded.
* This is more intuitive that requiring a LoadModule directive.
*/
"A cache type and partial URL prefix below which "
"caching is enabled"),
"A partial URL prefix below which caching is disabled"),
"The maximum time in seconds to cache a document"),
"The minimum time in seconds to cache a document"),
"The default time in seconds to cache a document"),
"Run the cache in the quick handler, default on"),
"Ignore Responses where there is no Last Modified Header"),
"Ignore requests from the client for uncached content"),
"Ignore expiration dates when populating cache, resulting in "
"an If-Modified-Since request to the backend on retrieval"),
"Ignore 'Cache-Control: private' and store private content"),
"Ignore 'Cache-Control: no-store' and store sensitive content"),
"A space separated list of headers that should not be "
"stored by the cache"),
"Ignore query-string when caching"),
"identifiers that should be ignored for creating the key "
"of the cached entity."),
"The factor used to estimate Expires date from "
"LastModified date"),
"Enable or disable the thundering herd lock."),
"The thundering herd lock path. Defaults to the '"
DEFAULT_CACHE_LOCKPATH "' directory relative to the "
"DefaultRuntimeDir setting."),
"Maximum age of any thundering herd lock."),
"Add a X-Cache header to responses. Default is off."),
"Add a X-Cache-Detail header to responses. Default is off."),
"Override the base URL of reverse proxied cache keys."),
"Serve stale content on 5xx errors if present. Defaults to on."),
{NULL}
};
static void register_hooks(apr_pool_t *p)
{
/* cache initializer */
/* cache quick handler */
/* cache handler */
/* cache status */
/* cache error handler */
/* cache filters
* XXX The cache filters need to run right after the handlers and before
* any other filters. Consider creating AP_FTYPE_CACHE for this purpose.
*
* Depending on the type of request (subrequest / main request) they
* need to be run before AP_FTYPE_CONTENT_SET / after AP_FTYPE_CONTENT_SET
* filters. Thus create two filter handles for each type:
* cache_save_filter_handle / cache_out_filter_handle to be used by
* main requests and
* cache_save_subreq_filter_handle / cache_out_subreq_filter_handle
* to be run by subrequest
*/
/*
* CACHE is placed into the filter chain at an admin specified location,
* and when the cache_handler is run, the CACHE filter is swapped with
* the CACHE_OUT filter, or CACHE_SAVE filter as appropriate. This has
* the effect of offering optional fine control of where the cache is
* inserted into the filter chain.
*/
ap_register_output_filter("CACHE",
NULL,
/*
* CACHE_SAVE must go into the filter chain after a possible DEFLATE
* filter to ensure that the compressed content is stored.
* Incrementing filter type by 1 ensures this happens.
*/
ap_register_output_filter("CACHE_SAVE",
NULL,
/*
* CACHE_SAVE_SUBREQ must go into the filter chain before SUBREQ_CORE to
* handle subrequsts. Decrementing filter type by 1 ensures this
* happens.
*/
ap_register_output_filter("CACHE_SAVE_SUBREQ",
NULL,
/*
* CACHE_OUT must go into the filter chain after a possible DEFLATE
* filter to ensure that already compressed cache objects do not
* get compressed again. Incrementing filter type by 1 ensures
* this happens.
*/
ap_register_output_filter("CACHE_OUT",
NULL,
/*
* CACHE_OUT_SUBREQ must go into the filter chain before SUBREQ_CORE to
* handle subrequsts. Decrementing filter type by 1 ensures this
* happens.
*/
ap_register_output_filter("CACHE_OUT_SUBREQ",
NULL,
/* CACHE_REMOVE_URL has to be a protocol filter to ensure that is
* run even if the response is a canned error message, which
* removes the content filters.
*/
ap_register_output_filter("CACHE_REMOVE_URL",
NULL,
}
{
create_dir_config, /* create per-directory config structure */
merge_dir_config, /* merge per-directory config structures */
create_cache_config, /* create per-server config structure */
merge_cache_config, /* merge per-server config structures */
cache_cmds, /* command apr_table_t */
};
)
(cache_handle_t *h, request_rec *r,