]> granicus.if.org Git - apache/blob - modules/cache/cache_storage.c
remove some unused variables and re-name cache_select_url() to simply
[apache] / modules / cache / cache_storage.c
1 /* Copyright 2001-2005 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #define CORE_PRIVATE
18
19 #include "mod_cache.h"
20
21 extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key;
22
23 extern module AP_MODULE_DECLARE_DATA cache_module;
24
25 /* -------------------------------------------------------------- */
26
27 /*
28  * delete all URL entities from the cache
29  *
30  */
31 int cache_remove_url(cache_request_rec *cache, apr_pool_t *p)
32 {
33     cache_provider_list *list;
34     cache_handle_t *h;
35
36     list = cache->providers;
37
38     /* Remove the stale cache entry if present. If not, we're
39      * being called from outside of a request; remove the 
40      * non-stalle handle.
41      */
42     h = cache->stale_handle ? cache->stale_handle : cache->handle;
43     if (!h) {
44        return OK;
45     }
46     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
47                  "cache: Removing url %s from the cache", h->cache_obj->key);
48
49     /* for each specified cache type, delete the URL */
50     while(list) {
51         list->provider->remove_url(h, p);
52         list = list->next;
53     }
54     return OK;
55 }
56
57
58 /*
59  * create a new URL entity in the cache
60  *
61  * It is possible to store more than once entity per URL. This
62  * function will always create a new entity, regardless of whether
63  * other entities already exist for the same URL.
64  *
65  * The size of the entity is provided so that a cache module can
66  * decide whether or not it wants to cache this particular entity.
67  * If the size is unknown, a size of -1 should be set.
68  */
69 int cache_create_entity(request_rec *r, char *url, apr_off_t size)
70 {
71     cache_provider_list *list;
72     cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t));
73     char *key;
74     apr_status_t rv;
75     cache_request_rec *cache = (cache_request_rec *) 
76                          ap_get_module_config(r->request_config, &cache_module);
77
78     rv = cache_generate_key(r, r->pool, &key);
79     if (rv != APR_SUCCESS) {
80         return rv;
81     }
82
83     list = cache->providers;
84     /* for each specified cache type, delete the URL */
85     while (list) {
86         switch (rv = list->provider->create_entity(h, r, key, size)) {
87         case OK: {
88             cache->handle = h;
89             cache->provider = list->provider;
90             cache->provider_name = list->provider_name;
91             return OK;
92         }
93         case DECLINED: {
94             list = list->next;
95             continue;
96         }
97         default: {
98             return rv;
99         }
100         }
101     }
102     return DECLINED;
103 }
104
105 static int set_cookie_doo_doo(void *v, const char *key, const char *val)
106 {
107     apr_table_addn(v, key, val);
108     return 1;
109 }
110
111 CACHE_DECLARE(void) ap_cache_accept_headers(cache_handle_t *h, request_rec *r,
112                                             int preserve_orig)
113 {
114     apr_table_t *cookie_table, *hdr_copy;
115     const char *v;
116
117     v = apr_table_get(h->resp_hdrs, "Content-Type");
118     if (v) {
119         ap_set_content_type(r, v);
120         apr_table_unset(h->resp_hdrs, "Content-Type");
121     }
122
123     /* If the cache gave us a Last-Modified header, we can't just
124      * pass it on blindly because of restrictions on future values.
125      */
126     v = apr_table_get(h->resp_hdrs, "Last-Modified");
127     if (v) {
128         ap_update_mtime(r, apr_date_parse_http(v));
129         ap_set_last_modified(r);
130         apr_table_unset(h->resp_hdrs, "Last-Modified");
131     }
132
133     /* The HTTP specification says that it is legal to merge duplicate
134      * headers into one.  Some browsers that support Cookies don't like
135      * merged headers and prefer that each Set-Cookie header is sent
136      * separately.  Lets humour those browsers by not merging.
137      * Oh what a pain it is.
138      */
139     cookie_table = apr_table_make(r->pool, 2);
140     apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out,
141                  "Set-Cookie", NULL);
142     apr_table_do(set_cookie_doo_doo, cookie_table, h->resp_hdrs,
143                  "Set-Cookie", NULL);
144     apr_table_unset(r->err_headers_out, "Set-Cookie");
145     apr_table_unset(h->resp_hdrs, "Set-Cookie");
146
147     if (preserve_orig) {
148         hdr_copy = apr_table_copy(r->pool, h->resp_hdrs);
149         apr_table_overlap(hdr_copy, r->headers_out, APR_OVERLAP_TABLES_SET);
150         r->headers_out = hdr_copy;
151     }
152     else {
153         apr_table_overlap(r->headers_out, h->resp_hdrs, APR_OVERLAP_TABLES_SET);
154     }
155     if (!apr_is_empty_table(cookie_table)) {
156         r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out,
157                                                cookie_table);
158     }
159 }
160
161 /*
162  * select a specific URL entity in the cache
163  *
164  * It is possible to store more than one entity per URL. Content
165  * negotiation is used to select an entity. Once an entity is
166  * selected, details of it are stored in the per request
167  * config to save time when serving the request later.
168  *
169  * This function returns OK if successful, DECLINED if no
170  * cached entity fits the bill.
171  */
172 int cache_select(request_rec *r)
173 {
174     cache_provider_list *list;
175     apr_status_t rv;
176     cache_handle_t *h;
177     char *key;
178     cache_request_rec *cache = (cache_request_rec *) 
179                          ap_get_module_config(r->request_config, &cache_module);
180
181     rv = cache_generate_key(r, r->pool, &key);
182     if (rv != APR_SUCCESS) {
183         return rv;
184     }
185     /* go through the cache types till we get a match */
186     h = apr_palloc(r->pool, sizeof(cache_handle_t));
187
188     list = cache->providers;
189
190     while (list) {
191         switch ((rv = list->provider->open_entity(h, r, key))) {
192         case OK: {
193             char *vary = NULL;
194             int fresh;
195
196             if (list->provider->recall_headers(h, r) != APR_SUCCESS) {
197                 /* TODO: Handle this error */
198                 return DECLINED;
199             }
200
201             /*
202              * Check Content-Negotiation - Vary
203              * 
204              * At this point we need to make sure that the object we found in
205              * the cache is the same object that would be delivered to the
206              * client, when the effects of content negotiation are taken into
207              * effect.
208              *
209              * In plain english, we want to make sure that a language-negotiated
210              * document in one language is not given to a client asking for a
211              * language negotiated document in a different language by mistake.
212              *
213              * This code makes the assumption that the storage manager will
214              * cache the req_hdrs if the response contains a Vary
215              * header.
216              *
217              * RFC2616 13.6 and 14.44 describe the Vary mechanism.
218              */
219             vary = apr_pstrdup(r->pool, apr_table_get(h->resp_hdrs, "Vary"));
220             while (vary && *vary) {
221                 char *name = vary;
222                 const char *h1, *h2;
223
224                 /* isolate header name */
225                 while (*vary && !apr_isspace(*vary) && (*vary != ','))
226                     ++vary;
227                 while (*vary && (apr_isspace(*vary) || (*vary == ','))) {
228                     *vary = '\0';
229                     ++vary;
230                 }
231
232                 /*
233                  * is this header in the request and the header in the cached
234                  * request identical? If not, we give up and do a straight get
235                  */
236                 h1 = apr_table_get(r->headers_in, name);
237                 h2 = apr_table_get(h->req_hdrs, name);
238                 if (h1 == h2) {
239                     /* both headers NULL, so a match - do nothing */
240                 }
241                 else if (h1 && h2 && !strcmp(h1, h2)) {
242                     /* both headers exist and are equal - do nothing */
243                 }
244                 else {
245                     /* headers do not match, so Vary failed */
246                     ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS,
247                                 r->server,
248                                 "cache_select_url(): Vary header mismatch.");
249                     return DECLINED;
250                 }
251             }
252
253             cache->provider = list->provider;
254             cache->provider_name = list->provider_name;
255
256             /* Is our cached response fresh enough? */
257             fresh = ap_cache_check_freshness(h, r);
258             if (!fresh) {
259                 const char *etag, *lastmod;
260
261                 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
262                   "Cached response for %s isn't fresh.  Adding/replacing "
263                   "conditional request headers.", r->uri);
264
265                 /* Make response into a conditional */
266                 cache->stale_headers = apr_table_copy(r->pool,
267                                                       r->headers_in);
268
269                 /* We can only revalidate with our own conditionals: remove the
270                  * conditions from the original request.
271                  */
272                 apr_table_unset(r->headers_in, "If-Match");
273                 apr_table_unset(r->headers_in, "If-Modified-Since");
274                 apr_table_unset(r->headers_in, "If-None-Match");
275                 apr_table_unset(r->headers_in, "If-Range");
276                 apr_table_unset(r->headers_in, "If-Unmodified-Since");
277
278                 etag = apr_table_get(h->resp_hdrs, "ETag");
279                 lastmod = apr_table_get(h->resp_hdrs, "Last-Modified");
280
281                 if (etag || lastmod) {
282                     /* If we have a cached etag and/or Last-Modified add in
283                      * our own conditionals.
284                      */
285
286                     if (etag) {
287                         apr_table_set(r->headers_in, "If-None-Match", etag);
288                     }
289                     
290                     if (lastmod) {
291                         apr_table_set(r->headers_in, "If-Modified-Since",
292                                       lastmod);
293                     }
294                     cache->stale_handle = h;
295                 }
296
297                 return DECLINED;
298             }
299
300             /* Okay, this response looks okay.  Merge in our stuff and go. */
301             ap_cache_accept_headers(h, r, 0);
302
303             cache->handle = h;
304             return OK;
305         }
306         case DECLINED: {
307             /* try again with next cache type */
308             list = list->next;
309             continue;
310         }
311         default: {
312             /* oo-er! an error */
313             return rv;
314         }
315         }
316     }
317     return DECLINED;
318 }
319
320 apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p,
321                                         char**key)
322 {
323     char *port_str, *scheme, *hn;
324     const char * hostname;
325     int i;
326
327     /* Use the canonical name to improve cache hit rate, but only if this is
328      * not a proxy request. 
329      */ 
330     if (!r->proxyreq) {
331         /* Use _default_ as the hostname if none present, as in mod_vhost */
332         hostname =  ap_get_server_name(r);
333         if (!hostname) {
334             hostname = "_default_";
335         }
336     }
337     else if(r->parsed_uri.hostname) {
338         /* Copy the parsed uri hostname */
339         hn = apr_pcalloc(p, strlen(r->parsed_uri.hostname) + 1);
340         for (i = 0; r->parsed_uri.hostname[i]; i++) {
341             hn[i] = apr_tolower(r->parsed_uri.hostname[i]);
342         }
343         /* const work-around */
344         hostname = hn;
345     }
346     else {
347         /* We are a proxied request, with no hostname. Unlikely
348          * to get very far - but just in case */
349         hostname = "_default_";
350     }
351
352     /* Copy the scheme, ensuring that it is lower case. If the parsed uri
353      * contains no string or if this is not a proxy request.
354      */
355     if (r->proxyreq && r->parsed_uri.scheme) {
356         /* Copy the scheme */
357         scheme = apr_pcalloc(p, strlen(r->parsed_uri.scheme) + 1);
358         for (i = 0; r->parsed_uri.scheme[i]; i++) {
359             scheme[i] = apr_tolower(r->parsed_uri.scheme[i]);
360         }
361     }
362     else {
363         scheme = "http";
364     }
365
366     /* If the content is locally generated, use the port-number of the
367      * current server. Otherwise. copy the URI's port-string (which may be a
368      * service name). If the URI contains no port-string, use apr-util's
369      * notion of the default port for that scheme - if available.
370      */
371     if(r->proxyreq) {
372         if (r->parsed_uri.port_str) {
373             port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2);
374             port_str[0] = ':';
375             for (i = 0; r->parsed_uri.port_str[i]; i++) {
376                 port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]);
377             }
378         }
379         else if (apr_uri_port_of_scheme(scheme)) {
380             port_str = apr_psprintf(p, ":%u", apr_uri_port_of_scheme(scheme));
381         }
382         else {
383             /* No port string given in the AbsoluteUri, and we have no
384              * idea what the default port for the scheme is. Leave it
385              * blank and live with the inefficiency of some extra cached
386              * entities.
387              */
388             port_str = "";
389         }       
390     }       
391     else {
392         /* Use the server port */
393         port_str = apr_psprintf(p, ":%u", ap_get_server_port(r));
394     }
395
396     /* Key format is a URI */
397     *key = apr_pstrcat(p, scheme, "://", hostname, port_str,
398                        r->parsed_uri.path, "?", r->args, NULL);
399
400     return APR_SUCCESS;
401 }