mod_cache.c revision c49200a2bdbb8fa1f2e5c3b87cc497ecdeefa8fe
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 The Apache Software Foundation. All rights
* reserved.
*
* 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
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE APACHE SOFTWARE FOUNDATION OR
* ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
#define CORE_PRIVATE
#include "mod_cache.h"
/* -------------------------------------------------------------- */
/*
* 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_IN filter
* If No:
* oh well.
*/
static int cache_url_handler(request_rec *r)
{
char *url = r->unparsed_uri;
const char *types;
&cache_module);
/* we don't handle anything but GET */
/*
* Which cache module (if any) should handle this request?
*/
return DECLINED;
}
/* make space for the per request config */
&cache_module);
if (!cache) {
}
/* save away the type */
/*
* 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.
*
* Note that there is a big difference between not being allowed
* to cache a request (no-store) and not being allowed to return
* a cached request without revalidation (max-age=0).
*
* Caching is forbidden under the following circumstances:
*
* - RFC2616 14.9.2 Cache-Control: no-store
* - Pragma: no-cache
* - Any requests requiring authorization.
* - Any URLs whose length exceeds MAX_URL_LENGTH
* - TODO: Make MAX_URL_LENGTH a config directive?
*/
"cache: URL exceeds length threshold: %s", url);
return DECLINED;
}
"incoming request is asking for a uncached version of %s, but we know better and are ignoring it", url);
}
else {
/* delete the previously cached file */
"cache: no-store forbids caching of %s", url);
return DECLINED;
}
}
/*
* Try serve this request from the cache.
*
* If no existing cache file
* add cache_in filter
* If stale cache file
* If conditional request
* add cache_in filter
* If non-conditional request
* fudge response into a conditional
* add cache_conditional filter
* If fresh cache file
* clear filter stack
* add cache_out filter
*/
/* no existing cache file */
"cache: no cache - add cache_in filter and DECLINE");
/* add cache_in filter to cache this request */
/* return DECLINED */
return DECLINED;
}
/* cache file exists */
/* fresh data available */
"cache: fresh cache - add cache_out filter and "
"handle request");
/* We are in the quick handler hook, which means that no output
* filters have been set. So lets run the insert_filter hook.
* XXX - Should we be inserting filters in the output stream
* for proxy requests? Certainly we need the core filters
* (byterange, chunking, etc.). I can also see the need to
* conditionally insert tag processing filters (e.g. INCLUDES).
*/
/* Now add the cache_out filter. cache_out is a FTYPE_CONTENT
* which means it will be inserted first in the stream, which
* is exactly what we need.
*/
/* kick off the filter stack */
"cache: error returned while trying to return %s "
"cached data",
return rv;
}
return OK;
}
else {
/* stale data available */
"cache: stale cache - test conditional");
/* if conditional request */
if (ap_cache_request_is_conditional(r)) {
r->server,
"cache: conditional - add cache_in filter and "
"DECLINE");
/* add cache_in filter */
/* return DECLINED */
return DECLINED;
}
/* else if non-conditional request */
else {
/* fudge response into a conditional */
r->server,
"cache: nonconditional - fudge conditional "
"by etag");
/* if we have a cached etag */
}
r->server,
"cache: nonconditional - fudge conditional "
"by lastmod");
/* if we have a cached IMS */
"If-Modified-Since",
}
else {
/* something else - pretend there was no cache */
r->server,
"cache: nonconditional - no cached "
/* add cache_in filter to cache this request */
/* return DECLINED */
return DECLINED;
}
/* add cache_conditional filter */
r->server,
"cache: nonconditional - add cache_conditional and"
" DECLINE");
ap_add_output_filter("CACHE_CONDITIONAL",
NULL,
r,
r->connection);
/* return DECLINED */
return DECLINED;
}
}
}
else {
/* error */
r->server,
"cache: error returned while checking for cached file by "
"%s cache",
return DECLINED;
}
}
/*
* CACHE_OUT filter
* ----------------
*
* Deliver cached content (headers and body) up the stack.
*/
{
request_rec *r = f->r;
&cache_module);
if (!cache) {
/* user likely configured CACHE_OUT manually; they should use mod_cache
* configuration to do that */
"CACHE_OUT enabled unexpectedly");
}
"cache: running CACHE_OUT filter");
/* TODO: Handle getting errors on either of these calls */
/* This filter is done once it has served up its content */
"cached version of %s being served", r->uri);
}
/*
* CACHE_CONDITIONAL filter
* ------------------------
*
* Decide whether or not cached content should be delivered
* based on our fudged conditional request.
* If response HTTP_NOT_MODIFIED
* replace ourselves with cache_out filter
* Otherwise
* replace ourselves with cache_in filter
*/
{
"cache: running CACHE_CONDITIONAL filter");
if (f->r->status == HTTP_NOT_MODIFIED) {
/* replace ourselves with CACHE_OUT filter */
}
else {
/* replace ourselves with CACHE_IN filter */
}
}
/*
* CACHE_IN filter
* ---------------
*
* Decide whether or not this content should be cached.
* If we decide no it should:
* remove the filter from the chain
* If we decide yes it should:
* pass the data to the storage manager
* pass the data to the next filter (the network)
*
*/
{
int rv;
request_rec *r = f->r;
char *url = r->unparsed_uri;
void *scache = r->request_config;
"cache: running CACHE_IN filter");
/* check first whether running this filter has any point or not */
if(r->no_cache) {
}
/* make space for the per request config
* We hit this code path when CACHE_IN has been installed by someone
* other than the cache handler
*/
if (!cache) {
}
/*
* Pass Data to Cache
* ------------------
* This section passes the brigades into the cache modules, but only
* if the setup section (see below) is complete.
*/
/* have we already run the cachability check and set up the cached file
* handle?
*/
if (cache->in_checked) {
/* pass the brigades into the cache, then pass them
* up the filter stack
*/
if (rv != APR_SUCCESS) {
}
}
/*
* 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.
*/
/* 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 */
}
}
else {
}
/* read the etag from the entity */
/*
* 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.
*/
/* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410
* We don't cache 206, because we don't (yet) cache partial responses.
* We include 304 Not Modified here too as this is the origin server
* telling us to serve the cached copy. */
r->status != HTTP_MULTIPLE_CHOICES &&
r->status != HTTP_MOVED_PERMANENTLY &&
r->status != HTTP_NOT_MODIFIED) ||
/* if a broken Expires header is present, don't cache it */
/* 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. */
*/
/* XXX mod-include clears last_modified/expires/etags - this is why we have
* a optional function for a key-gen ;-)
*/
&& (conf->no_last_mod_ignore ==0)) ||
/* HEAD requests */
r->header_only ||
/* RFC2616 14.9.2 Cache-Control: no-store response indicating do not
* cache, or stop now if you are trying to cache it */
/* RFC2616 14.9.1 Cache-Control: private
* this object is marked for this user's eyes only. Behave as a tunnel. */
/* 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
*/
) ||
/* or we've been asked not to cache it above */
r->no_cache) {
"cache: response is not cachable");
/* remove this object from the cache
* BillS Asks.. Why do we need to make this call to remove_url?
* leave it in for now..
*/
/* remove this filter from the chain */
/* ship the data up the stack */
}
/* Set the content length if known. We almost certainly do NOT want to
* cache streams with unknown content lengths in the in-memory cache.
* Streams with unknown content length should be first cached in the
* file system. If they are withing acceptable limits, then they can be
* moved to the in-memory cache.
*/
{
const char* cl;
if (cl) {
}
else {
/* if we don't get the content-length, see if we have all the
* buckets and use their length to calculate the size
*/
apr_bucket *e;
int all_buckets_here=0;
size=0;
APR_BRIGADE_FOREACH(e, in) {
if (APR_BUCKET_IS_EOS(e)) {
break;
}
if (APR_BUCKET_IS_FLUSH(e)) {
continue;
}
if (e->length < 0) {
break;
}
}
if (!all_buckets_here) {
size = -1;
}
}
}
/* 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->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).
*/
/* no cache handle, create a new entity */
}
/* pre-existing cache handle and 304, make entity fresh */
else if (r->status == HTTP_NOT_MODIFIED) {
/* update headers */
/* remove this filter ??? */
/* XXX is this right? we must set rv to something other than OK
* in this path
*/
}
/* pre-existing cache handle and new entity, replace entity with this one */
else {
}
/* Caching layer declined the opportunity to cache the response */
}
"cache: Caching url: %s", url);
/*
* 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.
*/
/* Read the date. Generate one if one is not supplied */
else
now = apr_time_now();
char *dates;
/* no date header! */
/* add one; N.B. use the time _now_ rather than when we were checking
* the cache
*/
"cache: Added date header");
}
else {
}
/* set response_time for HTTP/1.1 age calculations */
/* get the request time */
/* check last-modified date */
/* XXX FIXME we're referencing date on a path where we didn't set it */
{
/* if its in the future, then replace by date */
r->server,
"cache: Last modified is in the future, "
"replacing with now");
}
/* if no expiry date then
* if lastmod
* expiry date = now + min((date - lastmod) * factor, maxexpire)
* else
* expire date = now + defaultexpire
*/
if (exp == APR_DATE_BAD) {
if (lastmod != APR_DATE_BAD) {
}
else {
}
}
/*
* Write away header information to cache.
*/
if (rv == APR_SUCCESS) {
}
if (rv != APR_SUCCESS) {
}
}
/* -------------------------------------------------------------- */
/* Setup configurable data */
{
/* 1 if the cache is enabled, 0 otherwise */
ps->cacheon_set = 0;
/* array of URL prefixes for which caching is enabled */
/* array of URL prefixes for which caching is disabled */
/* maximum time to cache a document */
/* default time to cache a document */
/* factor used to estimate Expires date from LastModified date */
ps->factor_set = 0;
/* default percentage to force cache completion */
ps->complete_set = 0;
ps->no_last_mod_ignore_set = 0;
ps->no_last_mod_ignore = 0;
ps->ignorecachecontrol = 0;
ps->ignorecachecontrol_set = 0 ;
return ps;
}
{
/* 1 if the cache is enabled, 0 otherwise */
/* array of URL prefixes for which caching is disabled */
/* array of URL prefixes for which caching is enabled */
/* maximum time to cache a document */
/* default time to cache a document */
/* factor used to estimate Expires date from LastModified date */
/* default percentage to force cache completion */
return ps;
}
static const char
{
&cache_module);
return NULL;
}
static const char
{
&cache_module);
return NULL;
}
static const char
{
&cache_module);
return NULL;
}
static const char
void *dummy,
const char *type,
const char *url)
{
&cache_module);
struct cache_enable *new;
return NULL;
}
static const char
{
&cache_module);
struct cache_enable *new;
return NULL;
}
static const char
{
&cache_module);
return NULL;
}
static const char
{
&cache_module);
return NULL;
}
static const char
{
&cache_module);
double val;
return "CacheLastModifiedFactor value must be a float";
return NULL;
}
static const char
{
&cache_module);
int val;
return "CacheForceCompletion value must be a percentage";
return NULL;
}
static int
{
/* 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.
*/
"On if the transparent cache should be enabled"),
"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 default time in seconds to cache a document"),
"Ignore Responses where there is no Last Modified Header"),
"Ignore requests from the client for uncached content"),
"The factor used to estimate Expires date from LastModified date"),
"Percentage of download to arrive for the cache to force complete transfer"),
{NULL}
};
static void
{
/* cache initializer */
/* cache 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.
* Make them AP_FTYPE_CONTENT for now.
* XXX ianhH:they should run AFTER all the other content filters.
*/
ap_register_output_filter("CACHE_IN",
ap_register_output_filter("CACHE_OUT",
ap_register_output_filter("CACHE_CONDITIONAL",
}
{
NULL, /* create per-directory config structure */
NULL, /* 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 */
};