1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Author: mod_file_cache by Bill Stoddard <stoddard apache.org>
19 * Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org>
21 * v0.01: initial implementation
27 Some sites have a set of static files that are really busy, and
28 change infrequently (or even on a regular schedule). Save time
29 by caching open handles to these files. This module, unlike
30 mod_mmap_static, caches open file handles, not file content.
31 On systems (like Windows) with heavy system call overhead and
32 that have an efficient sendfile implementation, caching file handles
33 offers several advantages over caching content. First, the file system
34 can manage the memory, allowing infrequently hit cached files to
35 be paged out. Second, since caching open handles does not consume
36 significant resources, it will be possible to enable an AutoLoadCache
37 feature where static files are dynamically loaded in the cache
38 as the server runs. On systems that have file change notification,
39 this module can be enhanced to automatically garbage collect
40 cached files that change on disk.
42 This module should work on Unix systems that have sendfile. Place
43 cachefile directives into your configuration to direct files to
46 cachefile /path/to/file1
47 cachefile /path/to/file2
50 These files are only cached when the server is restarted, so if you
51 change the list, or if the files are changed, then you'll need to
54 To reiterate that point: if the files are modified *in place*
55 without restarting the server you may end up serving requests that
56 are completely bogus. You should update files by unlinking the old
57 copy and putting a new copy in place.
59 There's no such thing as inheriting these files across vhosts or
60 whatever... place the directives in the main server only.
64 Don't use Alias or RewriteRule to move these files around... unless
65 you feel like paying for an extra stat() on each request. This is
66 a deficiency in the Apache API that will hopefully be solved some day.
67 The file will be served out of the file handle cache, but there will be
68 an extra stat() that's a waste.
73 #if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
74 #error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
78 #include "apr_strings.h"
80 #include "apr_buckets.h"
82 #define APR_WANT_STRFUNC
85 #if APR_HAVE_SYS_TYPES_H
86 #include <sys/types.h>
90 #include "http_config.h"
92 #include "http_protocol.h"
93 #include "http_request.h"
94 #include "http_core.h"
96 module AP_MODULE_DECLARE_DATA file_cache_module;
102 const char *filename;
108 char mtimestr[APR_RFC822_DATE_LEN];
109 char sizestr[21]; /* big enough to hold any 64-bit file size + null */
117 static void *create_server_config(apr_pool_t *p, server_rec *s)
119 a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
121 sconf->fileht = apr_hash_make(p);
125 static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
127 a_server_config *sconf;
130 apr_file_t *fd = NULL;
134 fspec = ap_server_root_relative(cmd->pool, filename);
136 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
137 "mod_file_cache: invalid file path "
138 "%s, skipping", filename);
141 if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
142 cmd->temp_pool)) != APR_SUCCESS) {
143 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
144 "mod_file_cache: unable to stat(%s), skipping", fspec);
147 if (tmp.finfo.filetype != APR_REG) {
148 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
149 "mod_file_cache: %s isn't a regular file, skipping", fspec);
152 if (tmp.finfo.size > AP_MAX_SENDFILE) {
153 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
154 "mod_file_cache: %s is too large to cache, skipping", fspec);
158 rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
159 APR_OS_DEFAULT, cmd->pool);
160 if (rc != APR_SUCCESS) {
161 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
162 "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
165 apr_file_inherit_set(fd);
167 /* WooHoo, we have a file to put in the cache */
168 new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
169 new_file->finfo = tmp.finfo;
173 /* MMAPFile directive. MMAP'ing the file
174 * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
175 * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
177 if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
178 (apr_size_t)new_file->finfo.size,
179 APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
181 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
182 "mod_file_cache: unable to mmap %s, skipping", filename);
186 new_file->is_mmapped = TRUE;
191 /* CacheFile directive. Caching the file handle */
192 new_file->is_mmapped = FALSE;
197 new_file->filename = fspec;
198 apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
199 apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
201 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
202 apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
206 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
209 cache_the_file(cmd, filename, 0);
211 /* Sendfile not supported by this OS */
212 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
213 "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
217 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
220 cache_the_file(cmd, filename, 1);
222 /* MMAP not supported by this OS */
223 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
224 "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
229 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
230 apr_pool_t *ptemp, server_rec *s)
232 /* Hummm, anything to do here? */
236 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
237 * bit of a kludge, because we really want to run after core_translate runs.
239 static int file_cache_xlat(request_rec *r)
241 a_server_config *sconf;
245 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
247 /* we only operate when at least one cachefile directive was used */
248 if (!apr_hash_count(sconf->fileht)) {
252 res = ap_core_translate(r);
253 if (res != OK || !r->filename) {
257 /* search the cache */
258 match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
262 /* pass search results to handler */
263 ap_set_module_config(r->request_config, &file_cache_module, match);
265 /* shortcircuit the get_path_info() stat() calls and stuff */
266 r->finfo = match->finfo;
270 static int mmap_handler(request_rec *r, a_file *file)
273 conn_rec *c = r->connection;
276 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
278 apr_mmap_dup(&mm, file->mm, r->pool);
279 b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
281 APR_BRIGADE_INSERT_TAIL(bb, b);
282 b = apr_bucket_eos_create(c->bucket_alloc);
283 APR_BRIGADE_INSERT_TAIL(bb, b);
285 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
286 return HTTP_INTERNAL_SERVER_ERROR;
291 static int sendfile_handler(request_rec *r, a_file *file)
294 conn_rec *c = r->connection;
296 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
298 apr_brigade_insert_file(bb, file->file, 0, file->finfo.size, r->pool);
300 b = apr_bucket_eos_create(c->bucket_alloc);
301 APR_BRIGADE_INSERT_TAIL(bb, b);
303 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
304 return HTTP_INTERNAL_SERVER_ERROR;
309 static int file_cache_handler(request_rec *r)
315 /* XXX: not sure if this is right yet
316 * see comment in http_core.c:default_handler
318 if (ap_strcmp_match(r->handler, "*/*")) {
322 /* we don't handle anything but GET */
323 if (r->method_number != M_GET) return DECLINED;
325 /* did xlat phase find the file? */
326 match = ap_get_module_config(r->request_config, &file_cache_module);
332 /* note that we would handle GET on this resource */
333 r->allowed |= (AP_METHOD_BIT << M_GET);
335 /* This handler has no use for a request body (yet), but we still
336 * need to read and discard it if the client sent one.
338 if ((errstatus = ap_discard_request_body(r)) != OK)
341 ap_update_mtime(r, match->finfo.mtime);
343 /* ap_set_last_modified() always converts the file mtime to a string
344 * which is slow. Accelerate the common case.
345 * ap_set_last_modified(r);
351 mod_time = ap_rationalize_mtime(r, r->mtime);
352 if (mod_time == match->finfo.mtime)
353 datestr = match->mtimestr;
355 datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
356 apr_rfc822_date(datestr, mod_time);
358 apr_table_setn(r->headers_out, "Last-Modified", datestr);
361 /* ap_set_content_length() always converts the same number and never
362 * returns an error. Accelerate it.
364 r->clength = match->finfo.size;
365 apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
368 if ((errstatus = ap_meets_conditions(r)) != OK) {
372 /* Call appropriate handler */
373 if (!r->header_only) {
374 if (match->is_mmapped == TRUE)
375 rc = mmap_handler(r, match);
377 rc = sendfile_handler(r, match);
383 static command_rec file_cache_cmds[] =
385 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
386 "A space separated list of files to add to the file handle cache at config time"),
387 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
388 "A space separated list of files to mmap at config time"),
392 static void register_hooks(apr_pool_t *p)
394 ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
395 ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
396 ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
397 /* This trick doesn't work apparently because the translate hooks
398 are single shot. If the core_hook returns OK, then our hook is
400 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
405 AP_DECLARE_MODULE(file_cache) =
407 STANDARD20_MODULE_STUFF,
408 NULL, /* create per-directory config structure */
409 NULL, /* merge per-directory config structures */
410 create_server_config, /* create per-server config structure */
411 NULL, /* merge per-server config structures */
412 file_cache_cmds, /* command handlers */
413 register_hooks /* register hooks */