]> granicus.if.org Git - apache/blob - modules/cache/mod_file_cache.c
Goodbye ap_send_http_header
[apache] / modules / cache / mod_file_cache.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56  * Author: mod_file_cache by Bill Stoddard <stoddard@apache.org> 
57  *         Based on mod_mmap_static by Dean Gaudet <dgaudet@arctic.org>
58  *
59  * v0.01: initial implementation
60  */
61
62 /*
63     Documentation:
64
65     Some sites have a set of static files that are really busy, and 
66     change infrequently (or even on a regular schedule). Save time 
67     by caching open handles to these files. This module, unlike 
68     mod_mmap_static, caches open file handles, not file content. 
69     On systems (like Windows) with heavy system call overhead and
70     that have an efficient sendfile implementation, caching file handles
71     offers several advantages over caching content. First, the file system
72     can manage the memory, allowing infrequently hit cached files to
73     be paged out. Second, since caching open handles does not consume
74     significant resources, it will be possible to enable an AutoLoadCache
75     feature where static files are dynamically loaded in the cache 
76     as the server runs. On systems that have file change notification,
77     this module can be enhanced to automatically garbage collect 
78     cached files that change on disk.
79
80     This module should work on Unix systems that have sendfile. Place 
81     cachefile directives into your configuration to direct files to
82     be cached.
83
84         cachefile /path/to/file1
85         cachefile /path/to/file2
86         ...
87
88     These files are only cached when the server is restarted, so if you 
89     change the list, or if the files are changed, then you'll need to 
90     restart the server.
91
92     To reiterate that point:  if the files are modified *in place*
93     without restarting the server you may end up serving requests that
94     are completely bogus.  You should update files by unlinking the old
95     copy and putting a new copy in place. 
96
97     There's no such thing as inheriting these files across vhosts or
98     whatever... place the directives in the main server only.
99
100     Known problems:
101
102     Don't use Alias or RewriteRule to move these files around...  unless
103     you feel like paying for an extra stat() on each request.  This is
104     a deficiency in the Apache API that will hopefully be solved some day.
105     The file will be served out of the file handle cache, but there will be
106     an extra stat() that's a waste.
107 */
108
109 #include "apr.h"
110 #include "apr_mmap.h"
111 #include "apr_strings.h"
112
113 #define APR_WANT_STRFUNC
114 #include "apr_want.h"
115
116 #if APR_HAVE_SYS_TYPES_H
117 #include <sys/types.h>
118 #endif
119
120 #define CORE_PRIVATE
121
122 #include "httpd.h"
123 #include "http_config.h"
124 #include "http_log.h"
125 #include "http_protocol.h"
126 #include "http_request.h"
127 #include "http_core.h"
128
129 module AP_MODULE_DECLARE_DATA file_cache_module;
130
131 typedef struct {
132 #if APR_HAS_SENDFILE
133     apr_file_t *file;
134 #endif
135     const char *filename;
136     apr_finfo_t finfo;
137     int is_mmapped;
138 #if APR_HAS_MMAP
139     apr_mmap_t *mm;
140 #endif
141     char mtimestr[APR_RFC822_DATE_LEN];
142     char sizestr[21];   /* big enough to hold any 64-bit file size + null */ 
143 } a_file;
144
145 typedef struct {
146     apr_array_header_t *files;
147 } a_server_config;
148
149
150 static void *create_server_config(apr_pool_t *p, server_rec *s)
151 {
152     a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
153
154     sconf->files = apr_array_make(p, 20, sizeof(a_file));
155     return sconf;
156 }
157
158 static apr_status_t cleanup_file_cache(void *sconfv)
159 {
160     a_server_config *sconf = sconfv;
161     size_t n;
162     a_file *file;
163
164     n = sconf->files->nelts;
165     file = (a_file *)sconf->files->elts;
166     while(n) {
167 #if APR_HAS_MMAP
168         if (file->is_mmapped) { 
169             apr_mmap_delete(file->mm);
170         } 
171         else 
172 #endif 
173 #if APR_HAS_SENDFILE
174             apr_file_close(file->file); 
175 #endif
176             ++file;
177             --n;
178     }
179     return APR_SUCCESS;
180 }
181
182 static const char *cachefile(cmd_parms *cmd, void *dummy, const char *filename)
183
184 {
185     /* ToDo:
186      * Disable the file cache on a Windows 9X box. APR_HAS_SENDFILE will be
187      * defined in an Apache for Windows build, but apr_sendfile returns
188      * APR_ENOTIMPL on Windows 9X because TransmitFile is not available.
189      */
190
191 #if APR_HAS_SENDFILE
192     a_server_config *sconf;
193     a_file *new_file;
194     a_file tmp;
195     apr_file_t *fd = NULL;
196     apr_status_t rc;
197
198     /* canonicalize the file name? */
199     /* os_canonical... */
200     /* XXX: uh... yea, or expect them to be -very- accurate typists */
201     if ((rc = apr_stat(&tmp.finfo, filename, APR_FINFO_MIN, 
202                        cmd->temp_pool)) != APR_SUCCESS) {
203         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
204             "mod_file_cache: unable to stat(%s), skipping", filename);
205         return NULL;
206     }
207     if (tmp.finfo.filetype != APR_REG) {
208         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
209             "mod_file_cache: %s isn't a regular file, skipping", filename);
210         return NULL;
211     }
212
213     rc = apr_file_open(&fd, filename, APR_READ | APR_XTHREAD, APR_OS_DEFAULT, cmd->pool);
214     if (rc != APR_SUCCESS) {
215         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
216                      "mod_file_cache: unable to open(%s, O_RDONLY), skipping", filename);
217         return NULL;
218     }
219     tmp.file = fd;
220     tmp.filename = apr_pstrdup(cmd->pool, filename);
221     apr_rfc822_date(tmp.mtimestr, tmp.finfo.mtime);
222     apr_snprintf(tmp.sizestr, sizeof tmp.sizestr, "%" APR_OFF_T_FMT, tmp.finfo.size);
223     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
224     new_file = apr_array_push(sconf->files);
225     *new_file = tmp;
226     if (sconf->files->nelts == 1) {
227         /* first one, register the cleanup */
228         apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null);
229     }
230
231     new_file->is_mmapped = FALSE;
232
233     return NULL;
234 #else
235     /* Sendfile not supported on this platform */
236     ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
237                  "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
238     return NULL;
239 #endif
240 }
241
242 static const char *mmapfile(cmd_parms *cmd, void *dummy, const char *filename)
243 {
244 #if APR_HAS_MMAP
245     a_server_config *sconf;
246     a_file *new_file;
247     a_file tmp;
248     apr_file_t *fd = NULL;
249     apr_status_t rc;
250     const char *fspec;
251
252     fspec = ap_os_case_canonical_filename(cmd->pool, filename);
253     if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN, 
254                        cmd->temp_pool)) != APR_SUCCESS) {
255         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
256             "mod_file_cache: unable to stat(%s), skipping", filename);
257         return NULL;
258     }
259     if ((tmp.finfo.filetype) != APR_REG) {
260         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
261             "mod_file_cache: %s isn't a regular file, skipping", filename);
262         return NULL;
263     }
264     if ((rc = apr_file_open(&fd, fspec, APR_READ, APR_OS_DEFAULT, 
265                        cmd->temp_pool)) != APR_SUCCESS) { 
266         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
267                      "mod_file_cache: unable to open %s, skipping", 
268                      filename);
269         return NULL;
270     }
271     if ((rc = apr_mmap_create(&tmp.mm, fd, 0, tmp.finfo.size, APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) { 
272         apr_file_close(fd);
273         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
274             "mod_file_cache: unable to mmap %s, skipping", filename);
275         return NULL;
276     }
277     apr_file_close(fd);
278     tmp.filename = fspec;
279     apr_rfc822_date(tmp.mtimestr, tmp.finfo.mtime);
280     apr_snprintf(tmp.sizestr, sizeof tmp.sizestr, "%" APR_OFF_T_FMT, tmp.finfo.size);
281     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
282     new_file = apr_array_push(sconf->files);
283     *new_file = tmp;
284     if (sconf->files->nelts == 1) {
285         /* first one, register the cleanup */
286        apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null); 
287     }
288
289     new_file->is_mmapped = TRUE;
290
291     return NULL;
292 #else
293     /* MMAP not supported on this platform*/
294     return NULL;
295 #endif
296 }
297
298
299 static int file_compare(const void *av, const void *bv)
300 {
301     const a_file *a = av;
302     const a_file *b = bv;
303
304     return strcmp(a->filename, b->filename);
305 }
306
307 static void file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
308                                    apr_pool_t *ptemp, server_rec *s)
309 {
310     a_server_config *sconf;
311     a_file *elts;
312     int nelts;
313
314     /* sort the elements of the main_server, by filename */
315     sconf = ap_get_module_config(s->module_config, &file_cache_module);
316     elts = (a_file *)sconf->files->elts;
317     nelts = sconf->files->nelts;
318     qsort(elts, nelts, sizeof(a_file), file_compare);
319
320     /* and make the virtualhosts share the same thing */
321     for (s = s->next; s; s = s->next) {
322         ap_set_module_config(s->module_config, &file_cache_module, sconf);
323     }
324 }
325
326 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
327  * bit of a kludge, because we really want to run after core_translate runs.
328  */
329 static int file_cache_xlat(request_rec *r)
330 {
331     a_server_config *sconf;
332     a_file tmp;
333     a_file *match;
334     int res;
335
336     sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
337
338     /* we only operate when at least one cachefile directive was used */
339     if (apr_is_empty_table(sconf->files))
340         return DECLINED;
341
342     res = ap_core_translate(r);
343     if (res != OK || !r->filename) {
344         return res;
345     }
346
347     tmp.filename = r->filename;
348     match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts,
349         sizeof(a_file), file_compare);
350
351     if (match == NULL)
352         return DECLINED;
353
354     /* pass bsearch results to handler */
355     ap_set_module_config(r->request_config, &file_cache_module, match);
356
357     /* shortcircuit the get_path_info() stat() calls and stuff */
358     r->finfo = match->finfo;
359     return OK;
360 }
361
362
363 static int mmap_handler(request_rec *r, a_file *file)
364 {
365 #if APR_HAS_MMAP
366     ap_send_mmap (file->mm, r, 0, file->finfo.size);
367 #endif
368     return OK;
369 }
370
371 static int sendfile_handler(request_rec *r, a_file *file)
372 {
373 #if APR_HAS_SENDFILE
374     apr_size_t nbytes;
375     apr_status_t rv = APR_EINIT;
376
377     /* A cached file handle (more importantly, its file pointer) is 
378      * shared by all threads in the process. The file pointer will 
379      * be corrupted if multiple threads attempt to read from the 
380      * cached file handle. The sendfile API does not rely on the position 
381      * of the file pointer instead taking explicit file offset and 
382      * length arguments. 
383      *
384      * We should call ap_send_fd with a cached file handle IFF 
385      * we are CERTAIN the file will be served with apr_sendfile(). 
386      * The presense of an AP_FTYPE_FILTER in the filter chain nearly
387      * guarantees that apr_sendfile will NOT be used to send the file.
388      * Furthermore, AP_FTYPE_CONTENT filters will be at the beginning
389      * of the chain, so it should suffice to just check the first 
390      * filter in the chain. If the first filter is not a content filter, 
391      * assume apr_sendfile() will be used to send the content.
392      */
393     if (r->output_filters && r->output_filters->frec) {
394         if (r->output_filters->frec->ftype == AP_FTYPE_CONTENT)
395             return DECLINED;
396     }
397
398
399     rv = ap_send_fd(file->file, r, 0, file->finfo.size, &nbytes);
400     if (rv != APR_SUCCESS) {
401         /* ap_send_fd will log the error */
402         return HTTP_INTERNAL_SERVER_ERROR;
403     }
404 #endif
405     return OK;
406 }
407
408 static int file_cache_handler(request_rec *r) 
409 {
410     a_file *match;
411     int errstatus;
412     int rc = OK;
413
414     /* XXX: not sure if this is right yet
415      * see comment in http_core.c:default_handler
416      */
417     if (ap_strcmp_match(r->handler, "*/*")) {
418         return DECLINED;
419     }
420
421     /* we don't handle anything but GET */
422     if (r->method_number != M_GET) return DECLINED;
423
424     /* did xlat phase find the file? */
425     match = ap_get_module_config(r->request_config, &file_cache_module);
426
427     if (match == NULL) {
428         return DECLINED;
429     }
430
431     /* note that we would handle GET on this resource */
432     r->allowed |= (1 << M_GET);
433
434     /* This handler has no use for a request body (yet), but we still
435      * need to read and discard it if the client sent one.
436      */
437     if ((errstatus = ap_discard_request_body(r)) != OK)
438         return errstatus;
439
440     ap_update_mtime(r, match->finfo.mtime);
441
442     /* ap_set_last_modified() always converts the file mtime to a string
443      * which is slow.  Accelerate the common case.
444      * ap_set_last_modified(r);
445      */
446     {
447         apr_time_t mod_time;
448         char *datestr;
449
450         mod_time = ap_rationalize_mtime(r, r->mtime);
451         if (mod_time == match->finfo.mtime)
452             datestr = match->mtimestr;
453         else {
454             datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
455             apr_rfc822_date(datestr, mod_time);
456         }
457         apr_table_setn(r->headers_out, "Last-Modified", datestr);
458     }
459
460     ap_set_etag(r);
461     if ((errstatus = ap_meets_conditions(r)) != OK) {
462        return errstatus;
463     }
464
465     /* ap_set_content_length() always converts the same number and never
466      * returns an error.  Accelerate it.
467      */
468     r->clength = match->finfo.size;
469     apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
470
471     /* Call appropriate handler */
472     if (!r->header_only) {    
473         if (match->is_mmapped == TRUE)
474             rc = mmap_handler(r, match);
475         else
476             rc = sendfile_handler(r, match);
477     }
478
479     return rc;
480 }
481
482 static command_rec file_cache_cmds[] =
483 {
484 AP_INIT_ITERATE("cachefile", cachefile, NULL, RSRC_CONF,
485      "A space separated list of files to add to the file handle cache at config time"),
486 AP_INIT_ITERATE("mmapfile", mmapfile, NULL, RSRC_CONF,
487      "A space separated list of files to mmap at config time"),
488     {NULL}
489 };
490
491 static void register_hooks(apr_pool_t *p)
492 {
493     ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
494     ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
495     ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
496     /* This trick doesn't work apparently because the translate hooks
497        are single shot. If the core_hook returns OK, then our hook is 
498        not called.
499     ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE); 
500     */
501
502 }
503
504 module AP_MODULE_DECLARE_DATA file_cache_module =
505 {
506     STANDARD20_MODULE_STUFF,
507     NULL,                     /* create per-directory config structure */
508     NULL,                     /* merge per-directory config structures */
509     create_server_config,     /* create per-server config structure */
510     NULL,                     /* merge per-server config structures */
511     file_cache_cmds,          /* command handlers */
512     register_hooks            /* register hooks */
513 };