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 void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
167 a_server_config *sconf;
170 apr_file_t *fd = NULL;
174 fspec = ap_server_root_relative(cmd->pool, filename);
176 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
177 "mod_file_cache: invalid file path "
178 "%s, skipping", filename);
181 if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
182 cmd->temp_pool)) != APR_SUCCESS) {
183 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
184 "mod_file_cache: unable to stat(%s), skipping", fspec);
187 if (tmp.finfo.filetype != APR_REG) {
188 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
189 "mod_file_cache: %s isn't a regular file, skipping", fspec);
192 if (tmp.finfo.size > AP_MAX_SENDFILE) {
193 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
194 "mod_file_cache: %s is too large to cache, skipping", fspec);
198 rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
199 APR_OS_DEFAULT, cmd->pool);
200 if (rc != APR_SUCCESS) {
201 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
202 "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
205 apr_file_inherit_set(fd);
207 /* WooHoo, we have a file to put in the cache */
208 new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
209 new_file->finfo = tmp.finfo;
215 /* MMAPFile directive. MMAP'ing the file
216 * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
217 * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
219 if ((rc = apr_mmap_create(&mm, fd, 0,
220 (apr_size_t)new_file->finfo.size,
221 APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
223 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
224 "mod_file_cache: unable to mmap %s, skipping", filename);
228 /* We want to cache a duplicate apr_mmap_t to pass to each
229 * request so that nothing in the request will ever think that
230 * it's allowed to delete the mmap, since the "refcount" will
231 * never reach zero. */
232 /* XXX: the transfer_ownership flag on this call
233 * will go away soon.. it's ignored right now. */
234 apr_mmap_dup(&new_file->mm, mm, cmd->pool, 0);
235 new_file->is_mmapped = TRUE;
240 /* CacheFile directive. Caching the file handle */
241 new_file->is_mmapped = FALSE;
246 new_file->filename = fspec;
247 apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
248 apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
250 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
251 apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
255 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
258 cache_the_file(cmd, filename, 0);
260 /* Sendfile not supported by this OS */
261 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
262 "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
266 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
269 cache_the_file(cmd, filename, 1);
271 /* MMAP not supported by this OS */
272 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
273 "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
278 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
279 apr_pool_t *ptemp, server_rec *s)
281 /* Hummm, anything to do here? */
285 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
286 * bit of a kludge, because we really want to run after core_translate runs.
288 static int file_cache_xlat(request_rec *r)
290 a_server_config *sconf;
294 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
296 /* we only operate when at least one cachefile directive was used */
297 if (!apr_hash_count(sconf->fileht)) {
301 res = ap_core_translate(r);
302 if (res != OK || !r->filename) {
306 /* search the cache */
307 match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
311 /* pass search results to handler */
312 ap_set_module_config(r->request_config, &file_cache_module, match);
314 /* shortcircuit the get_path_info() stat() calls and stuff */
315 r->finfo = match->finfo;
319 static int mmap_handler(request_rec *r, a_file *file)
322 conn_rec *c = r->connection;
324 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
326 b = apr_bucket_mmap_create(file->mm, 0, (apr_size_t)file->finfo.size,
328 APR_BRIGADE_INSERT_TAIL(bb, b);
329 b = apr_bucket_eos_create(c->bucket_alloc);
330 APR_BRIGADE_INSERT_TAIL(bb, b);
332 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
333 return HTTP_INTERNAL_SERVER_ERROR;
338 static int sendfile_handler(request_rec *r, a_file *file)
341 conn_rec *c = r->connection;
343 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
345 b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
346 r->pool, c->bucket_alloc);
347 APR_BRIGADE_INSERT_TAIL(bb, b);
348 b = apr_bucket_eos_create(c->bucket_alloc);
349 APR_BRIGADE_INSERT_TAIL(bb, b);
351 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
352 return HTTP_INTERNAL_SERVER_ERROR;
357 static int file_cache_handler(request_rec *r)
363 /* XXX: not sure if this is right yet
364 * see comment in http_core.c:default_handler
366 if (ap_strcmp_match(r->handler, "*/*")) {
370 /* we don't handle anything but GET */
371 if (r->method_number != M_GET) return DECLINED;
373 /* did xlat phase find the file? */
374 match = ap_get_module_config(r->request_config, &file_cache_module);
380 /* note that we would handle GET on this resource */
381 r->allowed |= (AP_METHOD_BIT << M_GET);
383 /* This handler has no use for a request body (yet), but we still
384 * need to read and discard it if the client sent one.
386 if ((errstatus = ap_discard_request_body(r)) != OK)
389 ap_update_mtime(r, match->finfo.mtime);
391 /* ap_set_last_modified() always converts the file mtime to a string
392 * which is slow. Accelerate the common case.
393 * ap_set_last_modified(r);
399 mod_time = ap_rationalize_mtime(r, r->mtime);
400 if (mod_time == match->finfo.mtime)
401 datestr = match->mtimestr;
403 datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
404 apr_rfc822_date(datestr, mod_time);
406 apr_table_setn(r->headers_out, "Last-Modified", datestr);
410 if ((errstatus = ap_meets_conditions(r)) != OK) {
414 /* ap_set_content_length() always converts the same number and never
415 * returns an error. Accelerate it.
417 r->clength = match->finfo.size;
418 apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
420 /* Call appropriate handler */
421 if (!r->header_only) {
422 if (match->is_mmapped == TRUE)
423 rc = mmap_handler(r, match);
425 rc = sendfile_handler(r, match);
431 static command_rec file_cache_cmds[] =
433 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
434 "A space separated list of files to add to the file handle cache at config time"),
435 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
436 "A space separated list of files to mmap at config time"),
440 static void register_hooks(apr_pool_t *p)
442 ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
443 ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
444 ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
445 /* This trick doesn't work apparently because the translate hooks
446 are single shot. If the core_hook returns OK, then our hook is
448 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
453 module AP_MODULE_DECLARE_DATA file_cache_module =
455 STANDARD20_MODULE_STUFF,
456 NULL, /* create per-directory config structure */
457 NULL, /* merge per-directory config structures */
458 create_server_config, /* create per-server config structure */
459 NULL, /* merge per-server config structures */
460 file_cache_cmds, /* command handlers */
461 register_hooks /* register hooks */