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);
208
static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
211
cache_the_file(cmd, filename, 0);
213
/* Sendfile not supported by this OS */
214
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
215
"mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
219
static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
222
cache_the_file(cmd, filename, 1);
224
/* MMAP not supported by this OS */
225
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
226
"mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
231
static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
232
apr_pool_t *ptemp, server_rec *s)
234
/* Hummm, anything to do here? */
238
/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
239
* bit of a kludge, because we really want to run after core_translate runs.
241
static int file_cache_xlat(request_rec *r)
243
a_server_config *sconf;
247
sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
249
/* we only operate when at least one cachefile directive was used */
250
if (!apr_hash_count(sconf->fileht)) {
254
res = ap_core_translate(r);
255
if (res != OK || !r->filename) {
259
/* search the cache */
260
match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
264
/* pass search results to handler */
265
ap_set_module_config(r->request_config, &file_cache_module, match);
267
/* shortcircuit the get_path_info() stat() calls and stuff */
268
r->finfo = match->finfo;
272
static int mmap_handler(request_rec *r, a_file *file)
275
conn_rec *c = r->connection;
278
apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
280
apr_mmap_dup(&mm, file->mm, r->pool);
281
b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
283
APR_BRIGADE_INSERT_TAIL(bb, b);
284
b = apr_bucket_eos_create(c->bucket_alloc);
285
APR_BRIGADE_INSERT_TAIL(bb, b);
287
if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
288
return HTTP_INTERNAL_SERVER_ERROR;
293
static int sendfile_handler(request_rec *r, a_file *file)
296
conn_rec *c = r->connection;
298
apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
300
b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
301
r->pool, c->bucket_alloc);
302
APR_BRIGADE_INSERT_TAIL(bb, b);
303
b = apr_bucket_eos_create(c->bucket_alloc);
304
APR_BRIGADE_INSERT_TAIL(bb, b);
306
if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
307
return HTTP_INTERNAL_SERVER_ERROR;
312
static int file_cache_handler(request_rec *r)
318
/* XXX: not sure if this is right yet
319
* see comment in http_core.c:default_handler
321
if (ap_strcmp_match(r->handler, "*/*")) {
325
/* we don't handle anything but GET */
326
if (r->method_number != M_GET) return DECLINED;
328
/* did xlat phase find the file? */
329
match = ap_get_module_config(r->request_config, &file_cache_module);
335
/* note that we would handle GET on this resource */
336
r->allowed |= (AP_METHOD_BIT << M_GET);
338
/* This handler has no use for a request body (yet), but we still
339
* need to read and discard it if the client sent one.
341
if ((errstatus = ap_discard_request_body(r)) != OK)
344
ap_update_mtime(r, match->finfo.mtime);
346
/* ap_set_last_modified() always converts the file mtime to a string
347
* which is slow. Accelerate the common case.
348
* ap_set_last_modified(r);
354
mod_time = ap_rationalize_mtime(r, r->mtime);
355
if (mod_time == match->finfo.mtime)
356
datestr = match->mtimestr;
358
datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
359
apr_rfc822_date(datestr, mod_time);
361
apr_table_setn(r->headers_out, "Last-Modified", datestr);
365
if ((errstatus = ap_meets_conditions(r)) != OK) {
369
/* ap_set_content_length() always converts the same number and never
370
* returns an error. Accelerate it.
372
r->clength = match->finfo.size;
373
apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
375
/* Call appropriate handler */
376
if (!r->header_only) {
377
if (match->is_mmapped == TRUE)
378
rc = mmap_handler(r, match);
380
rc = sendfile_handler(r, match);
386
static command_rec file_cache_cmds[] =
388
AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
389
"A space separated list of files to add to the file handle cache at config time"),
390
AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
391
"A space separated list of files to mmap at config time"),
395
static void register_hooks(apr_pool_t *p)
397
ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
398
ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
399
ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
400
/* This trick doesn't work apparently because the translate hooks
401
are single shot. If the core_hook returns OK, then our hook is
403
ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
408
module AP_MODULE_DECLARE_DATA file_cache_module =
410
STANDARD20_MODULE_STUFF,
411
NULL, /* create per-directory config structure */
412
NULL, /* merge per-directory config structures */
413
create_server_config, /* create per-server config structure */
414
NULL, /* merge per-server config structures */
415
file_cache_cmds, /* command handlers */
416
register_hooks /* register hooks */