1 /* ====================================================================
2 * Copyright (c) 1998-1999 The Apache Group. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission. For written permission, please contact
26 * 5. Products derived from this software may not be called "Apache"
27 * nor may "Apache" appear in their names without prior written
28 * permission of the Apache Group.
30 * 6. Redistributions of any form whatsoever must retain the following
32 * "This product includes software developed by the Apache Group
33 * for use in the Apache HTTP server project (http://www.apache.org/)."
35 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Group and was originally based
51 * on public domain software written at the National Center for
52 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53 * For more information on the Apache Group and the Apache HTTP server
54 * project, please see <http://www.apache.org/>.
59 * Author: mod_file_cache by Bill Stoddard <stoddard@apache.org>
60 * Based on mod_mmap_static by Dean Gaudet <dgaudet@arctic.org>
62 * v0.01: initial implementation
68 Some sites have a set of static files that are really busy, and
69 change infrequently (or even on a regular schedule). Save time
70 by caching open handles to these files. This module, unlike
71 mod_mmap_static, caches open file handles, not file content.
72 On systems (like Windows) with heavy system call overhead and
73 that have an efficient sendfile implementation, caching file handles
74 offers several advantages over caching content. First, the file system
75 can manage the memory, allowing infrequently hit cached files to
76 be paged out. Second, since caching open handles does not consume
77 significant resources, it will be possible to enable an AutoLoadCache
78 feature where static files are dynamically loaded in the cache
79 as the server runs. On systems that have file change notification,
80 this module can be enhanced to automatically garbage collect
81 cached files that change on disk.
83 This module should work on Unix systems that have sendfile. Place
84 cachefile directives into your configuration to direct files to
87 cachefile /path/to/file1
88 cachefile /path/to/file2
91 These files are only cached when the server is restarted, so if you
92 change the list, or if the files are changed, then you'll need to
95 To reiterate that point: if the files are modified *in place*
96 without restarting the server you may end up serving requests that
97 are completely bogus. You should update files by unlinking the old
98 copy and putting a new copy in place.
100 There's no such thing as inheriting these files across vhosts or
101 whatever... place the directives in the main server only.
105 Don't use Alias or RewriteRule to move these files around... unless
106 you feel like paying for an extra stat() on each request. This is
107 a deficiency in the Apache API that will hopefully be solved some day.
108 The file will be served out of the file handle cache, but there will be
109 an extra stat() that's a waste.
115 #ifdef HAVE_SYS_TYPES_H
116 #include <sys/types.h>
125 #include "http_config.h"
126 #include "http_log.h"
127 #include "http_protocol.h"
128 #include "http_request.h"
129 #include "http_core.h"
130 #include "apr_mmap.h"
131 #include "apr_strings.h"
133 module AP_MODULE_DECLARE_DATA file_cache_module;
139 const char *filename;
148 apr_array_header_t *files;
152 static void *create_server_config(apr_pool_t *p, server_rec *s)
154 a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
156 sconf->files = apr_make_array(p, 20, sizeof(a_file));
161 static apr_status_t open_file(apr_file_t **file, const char *filename, int flg1, int flg2,
166 /* The Windows file needs to be opened for overlapped i/o, which APR doesn't
170 /* XXX: This is wrong for unicode FS ... and it doesn't belong in httpd */
171 hFile = CreateFile(filename, /* pointer to name of the file */
172 GENERIC_READ, /* access (read-write) mode */
173 FILE_SHARE_READ, /* share mode */
174 NULL, /* pointer to security attributes */
175 OPEN_EXISTING, /* how to create */
176 FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, /* file attributes */
177 NULL); /* handle to file with attributes to copy */
178 if (hFile != INVALID_HANDLE_VALUE) {
179 rv = apr_put_os_file(file, &hFile, p);
186 rv = apr_open(file, filename, flg1, flg2, p);
191 #endif /* APR_HAS_SENDFILE */
193 static apr_status_t cleanup_file_cache(void *sconfv)
195 a_server_config *sconf = sconfv;
199 n = sconf->files->nelts;
200 file = (a_file *)sconf->files->elts;
203 if (file->is_mmapped) {
204 apr_mmap_delete(file->mm);
209 apr_close(file->file);
217 static const char *cachefile(cmd_parms *cmd, void *dummy, const char *filename)
221 * Disable the file cache on a Windows 9X box. APR_HAS_SENDFILE will be
222 * defined in an Apache for Windows build, but apr_sendfile is not
223 * implemened on Windows 9X because TransmitFile is not available.
227 a_server_config *sconf;
230 apr_file_t *fd = NULL;
233 /* canonicalize the file name? */
234 /* os_canonical... */
235 if ((rc = apr_stat(&tmp.finfo, filename, cmd->temp_pool)) != APR_SUCCESS) {
236 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
237 "mod_file_cache: unable to stat(%s), skipping", filename);
240 if (tmp.finfo.filetype != APR_REG) {
241 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
242 "mod_file_cache: %s isn't a regular file, skipping", filename);
246 /* Note: open_file should call apr_open for Unix and CreateFile for Windows.
247 * The Windows file needs to be opened for async I/O to allow multiple threads
248 * to serve it up at once.
250 rc = open_file(&fd, filename, APR_READ, APR_OS_DEFAULT, cmd->pool);
251 if (rc != APR_SUCCESS) {
252 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
253 "mod_file_cache: unable to open(%s, O_RDONLY), skipping", filename);
257 tmp.filename = apr_pstrdup(cmd->pool, filename);
258 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
259 new_file = apr_push_array(sconf->files);
261 if (sconf->files->nelts == 1) {
262 /* first one, register the cleanup */
263 apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup);
266 new_file->is_mmapped = FALSE;
270 /* Sendfile not supported on this platform */
271 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
272 "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
277 static const char *mmapfile(cmd_parms *cmd, void *dummy, const char *filename)
280 a_server_config *sconf;
283 apr_file_t *fd = NULL;
287 fspec = ap_os_case_canonical_filename(cmd->pool, filename);
288 if ((rc = apr_stat(&tmp.finfo, fspec, cmd->temp_pool)) != APR_SUCCESS) {
289 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
290 "mod_file_cache: unable to stat(%s), skipping", filename);
293 if ((tmp.finfo.filetype) != APR_REG) {
294 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
295 "mod_file_cache: %s isn't a regular file, skipping", filename);
298 if ((rc = apr_open(&fd, fspec, APR_READ, APR_OS_DEFAULT,
299 cmd->temp_pool)) != APR_SUCCESS) {
300 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
301 "mod_file_cache: unable to open %s, skipping",
305 if ((rc = apr_mmap_create(&tmp.mm, fd, 0, tmp.finfo.size, APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
307 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
308 "mod_file_cache: unable to mmap %s, skipping", filename);
312 tmp.filename = fspec;
313 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
314 new_file = apr_push_array(sconf->files);
316 if (sconf->files->nelts == 1) {
317 /* first one, register the cleanup */
318 apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup);
321 new_file->is_mmapped = TRUE;
325 /* MMAP not supported on this platform*/
331 static int file_compare(const void *av, const void *bv)
333 const a_file *a = av;
334 const a_file *b = bv;
336 return strcmp(a->filename, b->filename);
339 static void file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
340 apr_pool_t *ptemp, server_rec *s)
342 a_server_config *sconf;
346 /* sort the elements of the main_server, by filename */
347 sconf = ap_get_module_config(s->module_config, &file_cache_module);
348 elts = (a_file *)sconf->files->elts;
349 nelts = sconf->files->nelts;
350 qsort(elts, nelts, sizeof(a_file), file_compare);
352 /* and make the virtualhosts share the same thing */
353 for (s = s->next; s; s = s->next) {
354 ap_set_module_config(s->module_config, &file_cache_module, sconf);
358 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
359 * bit of a kludge, because we really want to run after core_translate runs.
361 static int file_cache_xlat(request_rec *r)
363 a_server_config *sconf;
368 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
370 /* we only operate when at least one cachefile directive was used */
371 if (apr_is_empty_table(sconf->files))
374 res = ap_core_translate(r);
375 if (res != OK || !r->filename) {
379 tmp.filename = r->filename;
380 match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts,
381 sizeof(a_file), file_compare);
386 /* pass bsearch results to handler */
387 ap_set_module_config(r->request_config, &file_cache_module, match);
389 /* shortcircuit the get_path_info() stat() calls and stuff */
390 r->finfo = match->finfo;
395 static int mmap_handler(request_rec *r, a_file *file)
398 ap_send_mmap (file->mm, r, 0, file->finfo.size);
403 static int sendfile_handler(request_rec *r, a_file *file)
407 apr_status_t rv = APR_EINIT;
409 /* A cached file handle (more importantly, its file pointer) is
410 * shared by all threads in the process. The file pointer will
411 * be corrupted if multiple threads attempt to read from the
412 * cached file handle. The sendfile API does not rely on the position
413 * of the file pointer instead taking explicit file offset and
416 * We should call ap_send_fd with a cached file handle IFF
417 * we are CERTAIN the file will be served with apr_sendfile().
418 * The presense of an AP_FTYPE_FILTER in the filter chain nearly
419 * guarantees that apr_sendfile will NOT be used to send the file.
420 * Furthermore, AP_FTYPE_CONTENT filters will be at the beginning
421 * of the chain, so it should suffice to just check the first
422 * filter in the chain. If the first filter is not a content filter,
423 * assume apr_sendfile() will be used to send the content.
425 if (r->output_filters && r->output_filters->frec) {
426 if (r->output_filters->frec->ftype == AP_FTYPE_CONTENT)
431 rv = ap_send_fd(file->file, r, 0, file->finfo.size, &nbytes);
432 if (rv != APR_SUCCESS) {
433 /* ap_send_fd will log the error */
434 return HTTP_INTERNAL_SERVER_ERROR;
440 static int file_cache_handler(request_rec *r)
446 /* XXX: not sure if this is right yet
447 * see comment in http_core.c:default_handler
449 if (ap_strcmp_match(r->handler, "*/*")) {
453 /* we don't handle anything but GET */
454 if (r->method_number != M_GET) return DECLINED;
456 /* did xlat phase find the file? */
457 match = ap_get_module_config(r->request_config, &file_cache_module);
463 /* note that we would handle GET on this resource */
464 r->allowed |= (1 << M_GET);
466 /* This handler has no use for a request body (yet), but we still
467 * need to read and discard it if the client sent one.
469 if ((errstatus = ap_discard_request_body(r)) != OK)
472 ap_update_mtime(r, match->finfo.mtime);
473 ap_set_last_modified(r);
475 if ((errstatus = ap_meets_conditions(r)) != OK) {
478 ap_set_content_length(r, match->finfo.size);
480 ap_send_http_header(r);
482 /* Call appropriate handler */
483 if (!r->header_only) {
484 if (match->is_mmapped == TRUE)
485 rc = mmap_handler(r, match);
487 rc = sendfile_handler(r, match);
493 static command_rec file_cache_cmds[] =
495 AP_INIT_ITERATE("cachefile", cachefile, NULL, RSRC_CONF,
496 "A space separated list of files to add to the file handle cache at config time"),
497 AP_INIT_ITERATE("mmapfile", mmapfile, NULL, RSRC_CONF,
498 "A space separated list of files to mmap at config time"),
502 static void register_hooks(apr_pool_t *p)
504 ap_hook_handler(file_cache_handler, NULL, NULL, AP_HOOK_LAST);
505 ap_hook_post_config(file_cache_post_config, NULL, NULL, AP_HOOK_MIDDLE);
506 ap_hook_translate_name(file_cache_xlat, NULL, NULL, AP_HOOK_MIDDLE);
507 /* This trick doesn't work apparently because the translate hooks
508 are single shot. If the core_hook returns OK, then our hook is
510 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, AP_HOOK_MIDDLE);
515 module AP_MODULE_DECLARE_DATA file_cache_module =
517 STANDARD20_MODULE_STUFF,
518 NULL, /* create per-directory config structure */
519 NULL, /* merge per-directory config structures */
520 create_server_config, /* create per-server config structure */
521 NULL, /* merge per-server config structures */
522 file_cache_cmds, /* command handlers */
523 register_hooks /* register hooks */