1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
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
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.
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.
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.
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
47 * ====================================================================
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/>.
56 * Author: mod_file_cache by Bill Stoddard <stoddard@apache.org>
57 * Based on mod_mmap_static by Dean Gaudet <dgaudet@arctic.org>
59 * v0.01: initial implementation
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.
80 This module should work on Unix systems that have sendfile. Place
81 cachefile directives into your configuration to direct files to
84 cachefile /path/to/file1
85 cachefile /path/to/file2
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
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.
97 There's no such thing as inheriting these files across vhosts or
98 whatever... place the directives in the main server only.
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.
110 #include "apr_mmap.h"
111 #include "apr_strings.h"
113 #define APR_WANT_STRFUNC
114 #include "apr_want.h"
116 #if APR_HAVE_SYS_TYPES_H
117 #include <sys/types.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"
129 module AP_MODULE_DECLARE_DATA file_cache_module;
135 const char *filename;
141 char mtimestr[APR_RFC822_DATE_LEN];
142 char sizestr[21]; /* big enough to hold any 64-bit file size + null */
146 apr_array_header_t *files;
150 static void *create_server_config(apr_pool_t *p, server_rec *s)
152 a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
154 sconf->files = apr_array_make(p, 20, sizeof(a_file));
158 static apr_status_t cleanup_file_cache(void *sconfv)
160 a_server_config *sconf = sconfv;
164 n = sconf->files->nelts;
165 file = (a_file *)sconf->files->elts;
168 if (file->is_mmapped) {
169 apr_mmap_delete(file->mm);
174 apr_file_close(file->file);
182 static const char *cachefile(cmd_parms *cmd, void *dummy, const char *filename)
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.
192 a_server_config *sconf;
195 apr_file_t *fd = NULL;
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);
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);
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);
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);
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);
231 new_file->is_mmapped = FALSE;
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);
242 static const char *mmapfile(cmd_parms *cmd, void *dummy, const char *filename)
245 a_server_config *sconf;
248 apr_file_t *fd = NULL;
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);
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);
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",
271 if ((rc = apr_mmap_create(&tmp.mm, fd, 0, tmp.finfo.size, APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
273 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
274 "mod_file_cache: unable to mmap %s, skipping", filename);
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);
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);
289 new_file->is_mmapped = TRUE;
293 /* MMAP not supported on this platform*/
299 static int file_compare(const void *av, const void *bv)
301 const a_file *a = av;
302 const a_file *b = bv;
304 return strcmp(a->filename, b->filename);
307 static void file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
308 apr_pool_t *ptemp, server_rec *s)
310 a_server_config *sconf;
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);
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);
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.
329 static int file_cache_xlat(request_rec *r)
331 a_server_config *sconf;
336 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
338 /* we only operate when at least one cachefile directive was used */
339 if (apr_is_empty_table(sconf->files))
342 res = ap_core_translate(r);
343 if (res != OK || !r->filename) {
347 tmp.filename = r->filename;
348 match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts,
349 sizeof(a_file), file_compare);
354 /* pass bsearch results to handler */
355 ap_set_module_config(r->request_config, &file_cache_module, match);
357 /* shortcircuit the get_path_info() stat() calls and stuff */
358 r->finfo = match->finfo;
363 static int mmap_handler(request_rec *r, a_file *file)
366 ap_send_mmap (file->mm, r, 0, file->finfo.size);
371 static int sendfile_handler(request_rec *r, a_file *file)
375 apr_status_t rv = APR_EINIT;
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
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.
393 if (r->output_filters && r->output_filters->frec) {
394 if (r->output_filters->frec->ftype == AP_FTYPE_CONTENT)
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;
408 static int file_cache_handler(request_rec *r)
414 /* XXX: not sure if this is right yet
415 * see comment in http_core.c:default_handler
417 if (ap_strcmp_match(r->handler, "*/*")) {
421 /* we don't handle anything but GET */
422 if (r->method_number != M_GET) return DECLINED;
424 /* did xlat phase find the file? */
425 match = ap_get_module_config(r->request_config, &file_cache_module);
431 /* note that we would handle GET on this resource */
432 r->allowed |= (1 << M_GET);
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.
437 if ((errstatus = ap_discard_request_body(r)) != OK)
440 ap_update_mtime(r, match->finfo.mtime);
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);
450 mod_time = ap_rationalize_mtime(r, r->mtime);
451 if (mod_time == match->finfo.mtime)
452 datestr = match->mtimestr;
454 datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
455 apr_rfc822_date(datestr, mod_time);
457 apr_table_setn(r->headers_out, "Last-Modified", datestr);
461 if ((errstatus = ap_meets_conditions(r)) != OK) {
465 /* ap_set_content_length() always converts the same number and never
466 * returns an error. Accelerate it.
468 r->clength = match->finfo.size;
469 apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
471 ap_send_http_header(r);
473 /* Call appropriate handler */
474 if (!r->header_only) {
475 if (match->is_mmapped == TRUE)
476 rc = mmap_handler(r, match);
478 rc = sendfile_handler(r, match);
484 static command_rec file_cache_cmds[] =
486 AP_INIT_ITERATE("cachefile", cachefile, NULL, RSRC_CONF,
487 "A space separated list of files to add to the file handle cache at config time"),
488 AP_INIT_ITERATE("mmapfile", mmapfile, NULL, RSRC_CONF,
489 "A space separated list of files to mmap at config time"),
493 static void register_hooks(apr_pool_t *p)
495 ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
496 ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
497 ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
498 /* This trick doesn't work apparently because the translate hooks
499 are single shot. If the core_hook returns OK, then our hook is
501 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
506 module AP_MODULE_DECLARE_DATA file_cache_module =
508 STANDARD20_MODULE_STUFF,
509 NULL, /* create per-directory config structure */
510 NULL, /* merge per-directory config structures */
511 create_server_config, /* create per-server config structure */
512 NULL, /* merge per-server config structures */
513 file_cache_cmds, /* command handlers */
514 register_hooks /* register hooks */