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>
92 #include "http_config.h"
94 #include "http_protocol.h"
95 #include "http_request.h"
96 #include "http_core.h"
98 module AP_MODULE_DECLARE_DATA file_cache_module;
104 const char *filename;
110 char mtimestr[APR_RFC822_DATE_LEN];
111 char sizestr[21]; /* big enough to hold any 64-bit file size + null */
119 static void *create_server_config(apr_pool_t *p, server_rec *s)
121 a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
123 sconf->fileht = apr_hash_make(p);
127 static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
129 a_server_config *sconf;
132 apr_file_t *fd = NULL;
136 fspec = ap_server_root_relative(cmd->pool, filename);
138 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
139 "mod_file_cache: invalid file path "
140 "%s, skipping", filename);
143 if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
144 cmd->temp_pool)) != APR_SUCCESS) {
145 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
146 "mod_file_cache: unable to stat(%s), skipping", fspec);
149 if (tmp.finfo.filetype != APR_REG) {
150 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
151 "mod_file_cache: %s isn't a regular file, skipping", fspec);
154 if (tmp.finfo.size > AP_MAX_SENDFILE) {
155 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
156 "mod_file_cache: %s is too large to cache, skipping", fspec);
160 rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
161 APR_OS_DEFAULT, cmd->pool);
162 if (rc != APR_SUCCESS) {
163 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
164 "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
167 apr_file_inherit_set(fd);
169 /* WooHoo, we have a file to put in the cache */
170 new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
171 new_file->finfo = tmp.finfo;
175 /* MMAPFile directive. MMAP'ing the file
176 * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
177 * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
179 if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
180 (apr_size_t)new_file->finfo.size,
181 APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
183 ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
184 "mod_file_cache: unable to mmap %s, skipping", filename);
188 new_file->is_mmapped = TRUE;
193 /* CacheFile directive. Caching the file handle */
194 new_file->is_mmapped = FALSE;
199 new_file->filename = fspec;
200 apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
201 apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
203 sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
204 apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
207 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
210 cache_the_file(cmd, filename, 0);
212 /* Sendfile not supported by this OS */
213 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
214 "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
218 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
221 cache_the_file(cmd, filename, 1);
223 /* MMAP not supported by this OS */
224 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
225 "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
230 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
231 apr_pool_t *ptemp, server_rec *s)
233 /* Hummm, anything to do here? */
237 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
238 * bit of a kludge, because we really want to run after core_translate runs.
240 static int file_cache_xlat(request_rec *r)
242 a_server_config *sconf;
246 sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
248 /* we only operate when at least one cachefile directive was used */
249 if (!apr_hash_count(sconf->fileht)) {
253 res = ap_core_translate(r);
254 if (res != OK || !r->filename) {
258 /* search the cache */
259 match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
263 /* pass search results to handler */
264 ap_set_module_config(r->request_config, &file_cache_module, match);
266 /* shortcircuit the get_path_info() stat() calls and stuff */
267 r->finfo = match->finfo;
271 static int mmap_handler(request_rec *r, a_file *file)
274 conn_rec *c = r->connection;
277 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
279 apr_mmap_dup(&mm, file->mm, r->pool, 0);
280 b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
282 APR_BRIGADE_INSERT_TAIL(bb, b);
283 b = apr_bucket_eos_create(c->bucket_alloc);
284 APR_BRIGADE_INSERT_TAIL(bb, b);
286 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
287 return HTTP_INTERNAL_SERVER_ERROR;
292 static int sendfile_handler(request_rec *r, a_file *file)
295 conn_rec *c = r->connection;
297 apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
299 b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
300 r->pool, c->bucket_alloc);
301 APR_BRIGADE_INSERT_TAIL(bb, b);
302 b = apr_bucket_eos_create(c->bucket_alloc);
303 APR_BRIGADE_INSERT_TAIL(bb, b);
305 if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
306 return HTTP_INTERNAL_SERVER_ERROR;
311 static int file_cache_handler(request_rec *r)
317 /* XXX: not sure if this is right yet
318 * see comment in http_core.c:default_handler
320 if (ap_strcmp_match(r->handler, "*/*")) {
324 /* we don't handle anything but GET */
325 if (r->method_number != M_GET) return DECLINED;
327 /* did xlat phase find the file? */
328 match = ap_get_module_config(r->request_config, &file_cache_module);
334 /* note that we would handle GET on this resource */
335 r->allowed |= (AP_METHOD_BIT << M_GET);
337 /* This handler has no use for a request body (yet), but we still
338 * need to read and discard it if the client sent one.
340 if ((errstatus = ap_discard_request_body(r)) != OK)
343 ap_update_mtime(r, match->finfo.mtime);
345 /* ap_set_last_modified() always converts the file mtime to a string
346 * which is slow. Accelerate the common case.
347 * ap_set_last_modified(r);
353 mod_time = ap_rationalize_mtime(r, r->mtime);
354 if (mod_time == match->finfo.mtime)
355 datestr = match->mtimestr;
357 datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
358 apr_rfc822_date(datestr, mod_time);
360 apr_table_setn(r->headers_out, "Last-Modified", datestr);
364 if ((errstatus = ap_meets_conditions(r)) != OK) {
368 /* ap_set_content_length() always converts the same number and never
369 * returns an error. Accelerate it.
371 r->clength = match->finfo.size;
372 apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
374 /* Call appropriate handler */
375 if (!r->header_only) {
376 if (match->is_mmapped == TRUE)
377 rc = mmap_handler(r, match);
379 rc = sendfile_handler(r, match);
385 static command_rec file_cache_cmds[] =
387 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
388 "A space separated list of files to add to the file handle cache at config time"),
389 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
390 "A space separated list of files to mmap at config time"),
394 static void register_hooks(apr_pool_t *p)
396 ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
397 ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
398 ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
399 /* This trick doesn't work apparently because the translate hooks
400 are single shot. If the core_hook returns OK, then our hook is
402 ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
407 module AP_MODULE_DECLARE_DATA file_cache_module =
409 STANDARD20_MODULE_STUFF,
410 NULL, /* create per-directory config structure */
411 NULL, /* merge per-directory config structures */
412 create_server_config, /* create per-server config structure */
413 NULL, /* merge per-server config structures */
414 file_cache_cmds, /* command handlers */
415 register_hooks /* register hooks */