1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2002 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.
111 #if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
112 #error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
115 #include "apr_mmap.h"
116 #include "apr_strings.h"
117 #include "apr_hash.h"
118 #include "apr_buckets.h"
120 #define APR_WANT_STRFUNC
121 #include "apr_want.h"
123 #if APR_HAVE_SYS_TYPES_H
124 #include <sys/types.h>
130 #include "http_config.h"
131 #include "http_log.h"
132 #include "http_protocol.h"
133 #include "http_request.h"
134 #include "http_core.h"
136 module AP_MODULE_DECLARE_DATA file_cache_module;
142 const char *filename;
148 char mtimestr[APR_RFC822_DATE_LEN];
149 char sizestr[21]; /* big enough to hold any 64-bit file size + null */
157 static void *create_server_config(apr_pool_t *p, server_rec *s)
159 a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
161 sconf->fileht = apr_hash_make(p);
165 static apr_status_t cleanup_file_cache(void *sconfv)
167 a_server_config *sconf = sconfv;
168 apr_pool_t *p = apr_hash_pool_get(sconf->fileht);
170 apr_hash_index_t *hi;
172 /* Iterate over the file hash table and clean up each entry */
173 for (hi = apr_hash_first(p, sconf->fileht); hi; hi=apr_hash_next(hi)) {
174 apr_hash_this(hi, NULL, NULL, (void **)&file);
176 if (file->is_mmapped) {
177 apr_mmap_delete(file->mm);
181 if (!file->is_mmapped) {
182 apr_file_close(file->file);
189 static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
191 a_server_config *sconf;
194 apr_file_t *fd = NULL;
198 fspec = ap_server_root_relative(cmd->pool, filename);
200 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
201 "mod_file_cache: invalid file path "
202 "%s, skipping", filename);
205 if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
206 cmd->temp_pool)) != APR_SUCCESS) {
207 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
208 "mod_file_cache: unable to stat(%s), skipping", fspec);
211 if (tmp.finfo.filetype != APR_REG) {
212 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
213 "mod_file_cache: %s isn't a regular file, skipping", fspec);
216 if (tmp.finfo.size > AP_MAX_SENDFILE) {
217 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
218 "mod_file_cache: %s is too large to cache, skipping", fspec);
222 rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
223 APR_OS_DEFAULT, cmd->pool);
224 if (rc != APR_SUCCESS) {
225 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
226 "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
229 apr_file_set_inherit(fd);
231 /* WooHoo, we have a file to put in the cache */
232 new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
233 new_file->finfo = tmp.finfo;
239 /* MMAPFile directive. MMAP'ing the file
240 * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
241 * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
243 if ((rc = apr_mmap_create(&mm, fd, 0,
244 (apr_size_t)new_file->finfo.size,
245 APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
247 /* We want to cache an apr_mmap_t that's marked as "non-owner"
248 * to pass to each request so that mmap_setaside()'s call to
249 * apr_mmap_dup() will never try to move the apr_mmap_t to a
250 * different pool. This apr_mmap_t is already going to live
251 * longer than any request, but mmap_setaside() has no way to
252 * know that because it's allocated out of cmd->pool,
253 * which is disjoint from r->pool.
255 apr_mmap_dup(&new_file->mm, mm, cmd->pool, 0);
256 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
257 "mod_file_cache: unable to mmap %s, skipping", filename);
261 new_file->is_mmapped = TRUE;
266 /* CacheFile directive. Caching the file handle */
267 new_file->is_mmapped = FALSE;
272 new_file->filename = fspec;
273 apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
274 apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
276 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
277 apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
279 if (apr_hash_count(sconf->fileht) == 1) {
280 /* first one, register the cleanup */
281 apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null);
285 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
288 cache_the_file(cmd, filename, 0);
290 /* Sendfile not supported by this OS */
291 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
292 "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
296 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
299 cache_the_file(cmd, filename, 1);
301 /* MMAP not supported by this OS */
302 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
303 "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
308 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
309 apr_pool_t *ptemp, server_rec *s)
311 /* Hummm, anything to do here? */
315 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
316 * bit of a kludge, because we really want to run after core_translate runs.
318 static int file_cache_xlat(request_rec *r)
320 a_server_config *sconf;
324 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
326 /* we only operate when at least one cachefile directive was used */
327 if (!apr_hash_count(sconf->fileht)) {
331 res = ap_core_translate(r);
332 if (res != OK || !r->filename) {
336 /* search the cache */
337 match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
341 /* pass search results to handler */
342 ap_set_module_config(r->request_config, &file_cache_module, match);
344 /* shortcircuit the get_path_info() stat() calls and stuff */
345 r->finfo = match->finfo;
349 static int mmap_handler(request_rec *r, a_file *file)
352 conn_rec *c = r->connection;
354 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
356 b = apr_bucket_mmap_create(file->mm, 0, (apr_size_t)file->finfo.size,
358 APR_BRIGADE_INSERT_TAIL(bb, b);
359 b = apr_bucket_eos_create(c->bucket_alloc);
360 APR_BRIGADE_INSERT_TAIL(bb, b);
362 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
363 return HTTP_INTERNAL_SERVER_ERROR;
368 static int sendfile_handler(request_rec *r, a_file *file)
371 conn_rec *c = r->connection;
373 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
375 b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
376 r->pool, c->bucket_alloc);
377 APR_BRIGADE_INSERT_TAIL(bb, b);
378 b = apr_bucket_eos_create(c->bucket_alloc);
379 APR_BRIGADE_INSERT_TAIL(bb, b);
381 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
382 return HTTP_INTERNAL_SERVER_ERROR;
387 static int file_cache_handler(request_rec *r)
393 /* XXX: not sure if this is right yet
394 * see comment in http_core.c:default_handler
396 if (ap_strcmp_match(r->handler, "*/*")) {
400 /* we don't handle anything but GET */
401 if (r->method_number != M_GET) return DECLINED;
403 /* did xlat phase find the file? */
404 match = ap_get_module_config(r->request_config, &file_cache_module);
410 /* note that we would handle GET on this resource */
411 r->allowed |= (AP_METHOD_BIT << M_GET);
413 /* This handler has no use for a request body (yet), but we still
414 * need to read and discard it if the client sent one.
416 if ((errstatus = ap_discard_request_body(r)) != OK)
419 ap_update_mtime(r, match->finfo.mtime);
421 /* ap_set_last_modified() always converts the file mtime to a string
422 * which is slow. Accelerate the common case.
423 * ap_set_last_modified(r);
429 mod_time = ap_rationalize_mtime(r, r->mtime);
430 if (mod_time == match->finfo.mtime)
431 datestr = match->mtimestr;
433 datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
434 apr_rfc822_date(datestr, mod_time);
436 apr_table_setn(r->headers_out, "Last-Modified", datestr);
440 if ((errstatus = ap_meets_conditions(r)) != OK) {
444 /* ap_set_content_length() always converts the same number and never
445 * returns an error. Accelerate it.
447 r->clength = match->finfo.size;
448 apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
450 /* Call appropriate handler */
451 if (!r->header_only) {
452 if (match->is_mmapped == TRUE)
453 rc = mmap_handler(r, match);
455 rc = sendfile_handler(r, match);
461 static command_rec file_cache_cmds[] =
463 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
464 "A space separated list of files to add to the file handle cache at config time"),
465 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
466 "A space separated list of files to mmap at config time"),
470 static void register_hooks(apr_pool_t *p)
472 ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
473 ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
474 ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
475 /* This trick doesn't work apparently because the translate hooks
476 are single shot. If the core_hook returns OK, then our hook is
478 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
483 module AP_MODULE_DECLARE_DATA file_cache_module =
485 STANDARD20_MODULE_STUFF,
486 NULL, /* create per-directory config structure */
487 NULL, /* merge per-directory config structures */
488 create_server_config, /* create per-server config structure */
489 NULL, /* merge per-server config structures */
490 file_cache_cmds, /* command handlers */
491 register_hooks /* register hooks */