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.
17 #include "apr_file_io.h"
18 #include "apr_strings.h"
19 #include "mod_cache.h"
20 #include "mod_disk_cache.h"
21 #include "ap_provider.h"
22 #include "util_filter.h"
23 #include "util_script.h"
24 #include "util_charset.h"
27 #include <crcsync/crcsync.h>
30 * mod_disk_cache: Disk Based HTTP 1.1 Cache.
32 * Flow to Find the .data file:
33 * Incoming client requests URI /foo/bar/baz
34 * Generate <hash> off of /foo/bar/baz
36 * Read in <hash>.header file (may contain Format #1 or Format #2)
37 * If format #1 (Contains a list of Vary Headers):
38 * Use each header name (from .header) with our request values (headers_in) to
39 * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz
40 * re-read in <hash>.header (must be format #2)
44 * apr_uint32_t format;
46 * apr_array_t vary_headers (delimited by CRLF)
49 * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format)
50 * entity name (dobj->name) [length is in disk_cache_info_t->name_len]
51 * r->headers_out (delimited by CRLF)
53 * r->headers_in (delimited by CRLF)
57 module AP_MODULE_DECLARE_DATA crccache_client_module
;
59 /* Forward declarations */
60 static int remove_entity(cache_handle_t
*h
);
61 static apr_status_t
store_headers(cache_handle_t
*h
, request_rec
*r
,
63 static apr_status_t
store_body(cache_handle_t
*h
, request_rec
*r
,
64 apr_bucket_brigade
*b
);
65 static apr_status_t
recall_headers(cache_handle_t
*h
, request_rec
*r
);
66 static apr_status_t
recall_body(cache_handle_t
*h
, apr_pool_t
*p
,
67 apr_bucket_brigade
*bb
);
68 static apr_status_t
read_array(request_rec
*r
, apr_array_header_t
* arr
,
71 static ap_filter_rec_t
*crccache_decode_filter_handle
;
73 typedef enum decoding_state
{
75 DECODING_LITERAL_HEADER
,
77 DECODING_BLOCK_HEADER
,
81 typedef struct crccache_client_ctx_t
{
82 apr_bucket_brigade
*bb
;
86 unsigned section_length
;
87 unsigned processed_length
;
88 char section_header
[4];
89 // need to add pointer to file data here
90 } crccache_client_ctx
;
93 * Local static functions
96 static char *header_file(apr_pool_t
*p
, disk_cache_conf
*conf
,
97 disk_cache_object_t
*dobj
, const char *name
) {
98 if (!dobj
->hashfile
) {
99 dobj
->hashfile
= ap_cache_generate_name(p
, conf
->dirlevels
,
100 conf
->dirlength
, name
);
104 return apr_pstrcat(p
, dobj
->prefix
, CACHE_VDIR_SUFFIX
, "/",
105 dobj
->hashfile
, CACHE_HEADER_SUFFIX
, NULL
);
107 return apr_pstrcat(p
, conf
->cache_root
, "/", dobj
->hashfile
,
108 CACHE_HEADER_SUFFIX
, NULL
);
112 static char *data_file(apr_pool_t
*p
, disk_cache_conf
*conf
,
113 disk_cache_object_t
*dobj
, const char *name
) {
114 if (!dobj
->hashfile
) {
115 dobj
->hashfile
= ap_cache_generate_name(p
, conf
->dirlevels
,
116 conf
->dirlength
, name
);
120 return apr_pstrcat(p
, dobj
->prefix
, CACHE_VDIR_SUFFIX
, "/",
121 dobj
->hashfile
, CACHE_DATA_SUFFIX
, NULL
);
123 return apr_pstrcat(p
, conf
->cache_root
, "/", dobj
->hashfile
,
124 CACHE_DATA_SUFFIX
, NULL
);
128 static void mkdir_structure(disk_cache_conf
*conf
, const char *file
,
133 for (p
= (char*) file
+ conf
->cache_root_len
+ 1;;) {
139 rv
= apr_dir_make(file
, APR_UREAD
| APR_UWRITE
| APR_UEXECUTE
, pool
);
140 if (rv
!= APR_SUCCESS
&& !APR_STATUS_IS_EEXIST(rv
)) {
148 /* htcacheclean may remove directories underneath us.
149 * So, we'll try renaming three times at a cost of 0.002 seconds.
151 static apr_status_t
safe_file_rename(disk_cache_conf
*conf
, const char *src
,
152 const char *dest
, apr_pool_t
*pool
) {
155 rv
= apr_file_rename(src
, dest
, pool
);
157 if (rv
!= APR_SUCCESS
) {
160 for (i
= 0; i
< 2 && rv
!= APR_SUCCESS
; i
++) {
161 /* 1000 micro-seconds aka 0.001 seconds. */
164 mkdir_structure(conf
, dest
, pool
);
166 rv
= apr_file_rename(src
, dest
, pool
);
173 static apr_status_t
file_cache_el_final(disk_cache_object_t
*dobj
,
175 /* move the data over */
179 apr_file_close(dobj
->tfd
);
181 /* This assumes that the tempfile is on the same file system
182 * as the cache_root. If not, then we need a file copy/move
183 * rather than a rename.
185 rv
= apr_file_rename(dobj
->tempfile
, dobj
->datafile
, r
->pool
);
186 if (rv
!= APR_SUCCESS
) {
187 ap_log_error(APLOG_MARK
, APLOG_WARNING
, rv
,r
->server
, "disk_cache: rename tempfile to datafile failed:"
188 " %s -> %s", dobj
->tempfile
, dobj
->datafile
);
189 apr_file_remove(dobj
->tempfile
, r
->pool
);
198 static apr_status_t
file_cache_errorcleanup(disk_cache_object_t
*dobj
,
200 /* Remove the header file and the body file. */
201 apr_file_remove(dobj
->hdrsfile
, r
->pool
);
202 apr_file_remove(dobj
->datafile
, r
->pool
);
204 /* If we opened the temporary data file, close and remove it. */
206 apr_file_close(dobj
->tfd
);
207 apr_file_remove(dobj
->tempfile
, r
->pool
);
214 /* These two functions get and put state information into the data
215 * file for an ap_cache_el, this state information will be read
216 * and written transparent to clients of this module
218 static int file_cache_recall_mydata(apr_file_t
*fd
, cache_info
*info
,
219 disk_cache_object_t
*dobj
, request_rec
*r
) {
222 disk_cache_info_t disk_info
;
225 /* read the data from the cache file */
226 len
= sizeof(disk_cache_info_t
);
227 rv
= apr_file_read_full(fd
, &disk_info
, len
, &len
);
228 if (rv
!= APR_SUCCESS
) {
232 /* Store it away so we can get it later. */
233 dobj
->disk_info
= disk_info
;
235 info
->status
= disk_info
.status
;
236 info
->date
= disk_info
.date
;
237 info
->expire
= disk_info
.expire
;
238 info
->request_time
= disk_info
.request_time
;
239 info
->response_time
= disk_info
.response_time
;
241 /* Note that we could optimize this by conditionally doing the palloc
242 * depending upon the size. */
243 urlbuff
= apr_palloc(r
->pool
, disk_info
.name_len
+ 1);
244 len
= disk_info
.name_len
;
245 rv
= apr_file_read_full(fd
, urlbuff
, len
, &len
);
246 if (rv
!= APR_SUCCESS
) {
249 urlbuff
[disk_info
.name_len
] = '\0';
251 /* check that we have the same URL */
252 /* Would strncmp be correct? */
253 if (strcmp(urlbuff
, dobj
->name
) != 0) {
260 static const char* regen_key(apr_pool_t
*p
, apr_table_t
*headers
,
261 apr_array_header_t
*varray
, const char *oldkey
) {
268 nvec
= (varray
->nelts
* 2) + 1;
269 iov
= apr_palloc(p
, sizeof(struct iovec
) * nvec
);
270 elts
= (const char **) varray
->elts
;
273 * - Handle multiple-value headers better. (sort them?)
274 * - Handle Case in-sensitive Values better.
275 * This isn't the end of the world, since it just lowers the cache
276 * hit rate, but it would be nice to fix.
278 * The majority are case insenstive if they are values (encoding etc).
279 * Most of rfc2616 is case insensitive on header contents.
281 * So the better solution may be to identify headers which should be
282 * treated case-sensitive?
283 * HTTP URI's (3.2.3) [host and scheme are insensitive]
284 * HTTP method (5.1.1)
285 * HTTP-date values (3.3.1)
286 * 3.7 Media Types [exerpt]
287 * The type, subtype, and parameter attribute names are case-
288 * insensitive. Parameter values might or might not be case-sensitive,
289 * depending on the semantics of the parameter name.
290 * 4.20 Except [exerpt]
291 * Comparison of expectation values is case-insensitive for unquoted
292 * tokens (including the 100-continue token), and is case-sensitive for
293 * quoted-string expectation-extensions.
296 for (i
= 0, k
= 0; i
< varray
->nelts
; i
++) {
297 header
= apr_table_get(headers
, elts
[i
]);
301 iov
[k
].iov_base
= (char*) elts
[i
];
302 iov
[k
].iov_len
= strlen(elts
[i
]);
304 iov
[k
].iov_base
= (char*) header
;
305 iov
[k
].iov_len
= strlen(header
);
308 iov
[k
].iov_base
= (char*) oldkey
;
309 iov
[k
].iov_len
= strlen(oldkey
);
312 return apr_pstrcatv(p
, iov
, k
, NULL
);
315 static int array_alphasort(const void *fn1
, const void *fn2
) {
316 return strcmp(*(char**) fn1
, *(char**) fn2
);
319 static void tokens_to_array(apr_pool_t
*p
, const char *data
,
320 apr_array_header_t
*arr
) {
323 while ((token
= ap_get_list_item(p
, &data
)) != NULL
) {
324 *((const char **) apr_array_push(arr
)) = token
;
327 /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
328 qsort((void *) arr
->elts
, arr
->nelts
, sizeof(char *), array_alphasort
);
332 * Hook and mod_cache callback functions
334 static int create_entity(cache_handle_t
*h
, request_rec
*r
, const char *key
,
336 disk_cache_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
337 &crccache_client_module
);
339 disk_cache_object_t
*dobj
;
341 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "create_entity");
343 if (conf
->cache_root
== NULL
) {
347 /* Allocate and initialize cache_object_t and disk_cache_object_t */
348 h
->cache_obj
= obj
= apr_pcalloc(r
->pool
, sizeof(*obj
));
349 obj
->vobj
= dobj
= apr_pcalloc(r
->pool
, sizeof(*dobj
));
351 obj
->key
= apr_pstrdup(r
->pool
, key
);
353 dobj
->name
= obj
->key
;
355 /* Save the cache root */
356 dobj
->root
= apr_pstrndup(r
->pool
, conf
->cache_root
, conf
->cache_root_len
);
357 dobj
->root_len
= conf
->cache_root_len
;
358 dobj
->datafile
= data_file(r
->pool
, conf
, dobj
, key
);
359 dobj
->hdrsfile
= header_file(r
->pool
, conf
, dobj
, key
);
360 dobj
->tempfile
= apr_pstrcat(r
->pool
, conf
->cache_root
, AP_TEMPFILE
, NULL
);
365 static int open_entity(cache_handle_t
*h
, request_rec
*r
, const char *key
) {
370 static int error_logged
= 0;
371 disk_cache_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
372 &crccache_client_module
);
376 disk_cache_object_t
*dobj
;
378 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "open_entity");
382 /* Look up entity keyed to 'url' */
383 if (conf
->cache_root
== NULL
) {
386 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,
387 "disk_cache: Cannot cache files to disk without a CacheRoot specified.");
392 /* Create and init the cache object */
393 h
->cache_obj
= obj
= apr_pcalloc(r
->pool
, sizeof(cache_object_t
));
394 obj
->vobj
= dobj
= apr_pcalloc(r
->pool
, sizeof(disk_cache_object_t
));
398 /* Open the headers file */
401 /* Save the cache root */
402 dobj
->root
= apr_pstrndup(r
->pool
, conf
->cache_root
, conf
->cache_root_len
);
403 dobj
->root_len
= conf
->cache_root_len
;
405 dobj
->hdrsfile
= header_file(r
->pool
, conf
, dobj
, key
);
406 flags
= APR_READ
|APR_BINARY
|APR_BUFFERED
;
407 rc
= apr_file_open(&dobj
->hfd
, dobj
->hdrsfile
, flags
, 0, r
->pool
);
408 if (rc
!= APR_SUCCESS
) {
412 /* read the format from the cache file */
413 len
= sizeof(format
);
414 apr_file_read_full(dobj
->hfd
, &format
, len
, &len
);
416 if (format
== VARY_FORMAT_VERSION
) {
417 apr_array_header_t
* varray
;
420 len
= sizeof(expire
);
421 apr_file_read_full(dobj
->hfd
, &expire
, len
, &len
);
423 varray
= apr_array_make(r
->pool
, 5, sizeof(char*));
424 rc
= read_array(r
, varray
, dobj
->hfd
);
425 if (rc
!= APR_SUCCESS
) {
426 ap_log_error(APLOG_MARK
, APLOG_ERR
, rc
, r
->server
,
427 "disk_cache: Cannot parse vary header file: %s",
431 apr_file_close(dobj
->hfd
);
433 nkey
= regen_key(r
->pool
, r
->headers_in
, varray
, key
);
435 dobj
->hashfile
= NULL
;
436 dobj
->prefix
= dobj
->hdrsfile
;
437 dobj
->hdrsfile
= header_file(r
->pool
, conf
, dobj
, nkey
);
439 flags
= APR_READ
|APR_BINARY
|APR_BUFFERED
;
440 rc
= apr_file_open(&dobj
->hfd
, dobj
->hdrsfile
, flags
, 0, r
->pool
);
441 if (rc
!= APR_SUCCESS
) {
445 else if (format
!= DISK_FORMAT_VERSION
) {
446 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,
447 "cache_disk: File '%s' has a version mismatch. File had version: %d.",
448 dobj
->hdrsfile
, format
);
452 apr_off_t offset
= 0;
453 /* This wasn't a Vary Format file, so we must seek to the
454 * start of the file again, so that later reads work.
456 apr_file_seek(dobj
->hfd
, APR_SET
, &offset
);
463 dobj
->datafile
= data_file(r
->pool
, conf
, dobj
, nkey
);
464 dobj
->tempfile
= apr_pstrcat(r
->pool
, conf
->cache_root
, AP_TEMPFILE
, NULL
);
466 /* Open the data file */
467 flags
= APR_READ
|APR_BINARY
;
468 #ifdef APR_SENDFILE_ENABLED
469 flags
|= APR_SENDFILE_ENABLED
;
471 rc
= apr_file_open(&dobj
->fd
, dobj
->datafile
, flags
, 0, r
->pool
);
472 if (rc
!= APR_SUCCESS
) {
473 /* XXX: Log message */
477 rc
= apr_file_info_get(&finfo
, APR_FINFO_SIZE
, dobj
->fd
);
478 if (rc
== APR_SUCCESS
) {
479 dobj
->file_size
= finfo
.size
;
482 /* Read the bytes to setup the cache_info fields */
483 rc
= file_cache_recall_mydata(dobj
->hfd
, info
, dobj
, r
);
484 if (rc
!= APR_SUCCESS
) {
485 /* XXX log message */
489 /* Initialize the cache_handle callback functions */
490 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
491 "disk_cache: Recalled cached URL info header %s", dobj
->name
);
495 static int remove_entity(cache_handle_t
*h
) {
496 /* Null out the cache object pointer so next time we start from scratch */
498 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,"remove_entity" );
503 static int remove_url(cache_handle_t
*h
, apr_pool_t
*p
) {
505 disk_cache_object_t
*dobj
;
507 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,"remove_url" );
509 /* Get disk cache object from cache handle */
510 dobj
= (disk_cache_object_t
*) h
->cache_obj
->vobj
;
515 /* Delete headers file */
516 if (dobj
->hdrsfile
) {
517 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,
518 "disk_cache: Deleting %s from cache.", dobj
->hdrsfile
);
520 rc
= apr_file_remove(dobj
->hdrsfile
, p
);
521 if ((rc
!= APR_SUCCESS
) && !APR_STATUS_IS_ENOENT(rc
)) {
522 /* Will only result in an output if httpd is started with -e debug.
523 * For reason see log_error_core for the case s == NULL.
525 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, rc
, NULL
,
526 "disk_cache: Failed to delete headers file %s from cache.",
532 /* Delete data file */
533 if (dobj
->datafile
) {
534 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,
535 "disk_cache: Deleting %s from cache.", dobj
->datafile
);
537 rc
= apr_file_remove(dobj
->datafile
, p
);
538 if ((rc
!= APR_SUCCESS
) && !APR_STATUS_IS_ENOENT(rc
)) {
539 /* Will only result in an output if httpd is started with -e debug.
540 * For reason see log_error_core for the case s == NULL.
542 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, rc
, NULL
,
543 "disk_cache: Failed to delete data file %s from cache.",
549 /* now delete directories as far as possible up to our cache root */
551 const char *str_to_copy
;
553 str_to_copy
= dobj
->hdrsfile
? dobj
->hdrsfile
: dobj
->datafile
;
555 char *dir
, *slash
, *q
;
557 dir
= apr_pstrdup(p
, str_to_copy
);
559 /* remove filename */
560 slash
= strrchr(dir
, '/');
564 * now walk our way back to the cache root, delete everything
565 * in the way as far as possible
567 * Note: due to the way we constructed the file names in
568 * header_file and data_file, we are guaranteed that the
569 * cache_root is suffixed by at least one '/' which will be
570 * turned into a terminating null by this loop. Therefore,
571 * we won't either delete or go above our cache root.
573 for (q
= dir
+ dobj
->root_len
; *q
; ) {
574 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,
575 "disk_cache: Deleting directory %s from cache",
578 rc
= apr_dir_remove(dir
, p
);
579 if (rc
!= APR_SUCCESS
&& !APR_STATUS_IS_ENOENT(rc
)) {
582 slash
= strrchr(q
, '/');
591 static apr_status_t
read_array(request_rec
*r
, apr_array_header_t
* arr
,
593 char w
[MAX_STRING_LEN
];
597 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "read_array");
600 rv
= apr_file_gets(w
, MAX_STRING_LEN
- 1, file
);
601 if (rv
!= APR_SUCCESS
) {
602 ap_log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
603 "Premature end of vary array.");
608 if (p
> 0 && w
[p
- 1] == '\n') {
609 if (p
> 1 && w
[p
- 2] == CR
) {
617 /* If we've finished reading the array, break out of the loop. */
622 *((const char **) apr_array_push(arr
)) = apr_pstrdup(r
->pool
, w
);
628 static apr_status_t
store_array(apr_file_t
*fd
, apr_array_header_t
* arr
) {
634 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,"store)array" );
636 elts
= (const char **) arr
->elts
;
638 for (i
= 0; i
< arr
->nelts
; i
++) {
639 iov
[0].iov_base
= (char*) elts
[i
];
640 iov
[0].iov_len
= strlen(elts
[i
]);
641 iov
[1].iov_base
= CRLF
;
642 iov
[1].iov_len
= sizeof(CRLF
) - 1;
644 rv
= apr_file_writev(fd
, (const struct iovec
*) &iov
, 2,
646 if (rv
!= APR_SUCCESS
) {
651 iov
[0].iov_base
= CRLF
;
652 iov
[0].iov_len
= sizeof(CRLF
) - 1;
654 return apr_file_writev(fd
, (const struct iovec
*) &iov
, 1,
658 static apr_status_t
read_table(cache_handle_t
*handle
, request_rec
*r
,
659 apr_table_t
*table
, apr_file_t
*file
) {
660 char w
[MAX_STRING_LEN
];
664 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "read_table");
668 /* ### What about APR_EOF? */
669 rv
= apr_file_gets(w
, MAX_STRING_LEN
- 1, file
);
670 if (rv
!= APR_SUCCESS
) {
671 ap_log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
672 "Premature end of cache headers.");
676 /* Delete terminal (CR?)LF */
679 /* Indeed, the host's '\n':
680 '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
681 -- whatever the script generates.
683 if (p
> 0 && w
[p
- 1] == '\n') {
684 if (p
> 1 && w
[p
- 2] == CR
) {
692 /* If we've finished reading the headers, break out of the loop. */
697 #if APR_CHARSET_EBCDIC
698 /* Chances are that we received an ASCII header text instead of
699 * the expected EBCDIC header lines. Try to auto-detect:
701 if (!(l
= strchr(w
, ':'))) {
702 int maybeASCII
= 0, maybeEBCDIC
= 0;
703 unsigned char *cp
, native
;
704 apr_size_t inbytes_left
, outbytes_left
;
706 for (cp
= w
; *cp
!= '\0'; ++cp
) {
707 native
= apr_xlate_conv_byte(ap_hdrs_from_ascii
, *cp
);
708 if (apr_isprint(*cp
) && !apr_isprint(native
))
710 if (!apr_isprint(*cp
) && apr_isprint(native
))
713 if (maybeASCII
> maybeEBCDIC
) {
714 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,
715 "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
717 inbytes_left
= outbytes_left
= cp
- w
;
718 apr_xlate_conv_buffer(ap_hdrs_from_ascii
,
719 w
, &inbytes_left
, w
, &outbytes_left
);
722 #endif /*APR_CHARSET_EBCDIC*/
724 /* if we see a bogus header don't ignore it. Shout and scream */
725 if (!(l
= strchr(w
, ':'))) {
730 while (*l
&& apr_isspace(*l
)) {
734 apr_table_add(table
, w
, l
);
741 * Reads headers from a buffer and returns an array of headers.
742 * Returns NULL on file error
743 * This routine tries to deal with too long lines and continuation lines.
744 * @@@: XXX: FIXME: currently the headers are passed thru un-merged.
745 * Is that okay, or should they be collapsed where possible?
747 static apr_status_t
recall_headers(cache_handle_t
*h
, request_rec
*r
) {
753 disk_cache_object_t
*dobj
= (disk_cache_object_t
*) h
->cache_obj
->vobj
;
755 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "recall_headers");
757 /* This case should not happen... */
759 /* XXX log message */
763 h
->req_hdrs
= apr_table_make(r
->pool
, 20);
764 h
->resp_hdrs
= apr_table_make(r
->pool
, 20);
766 /* Call routine to read the header lines/status line */
767 read_table(h
, r
, h
->resp_hdrs
, dobj
->hfd
);
768 read_table(h
, r
, h
->req_hdrs
, dobj
->hfd
);
770 // TODO: JUST FOR DEBUGGING
771 apr_table_set(h
->resp_hdrs
, "Cache-Control", "max-age=30");
773 // TODO: We only really want to add our block hashes if the cache is not fresh
774 // TODO: We could achieve that by adding a filter here on sending the request
775 // and then doing all of this in the filter 'JIT'
776 e
= apr_bucket_file_create(dobj
->fd
, 0, (apr_size_t
) dobj
->file_size
, r
->pool
,
777 r
->connection
->bucket_alloc
);
780 apr_bucket_read(e
, &data
, &len
, APR_BLOCK_READ
);
782 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
783 "crccache: generatinf hash, read %ld bytes",len
);
785 // this will be rounded down, but thats okay
786 size_t blocksize
= len
/BLOCK_COUNT
;
788 // sanity check for very small files
791 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
792 "crccache: %d blocks of %ld bytes",BLOCK_COUNT
,blocksize
);
794 // add one for base 64 overflow and null terminator
795 char hash_set
[HASH_HEADER_SIZE
+HASH_BASE64_SIZE_PADDING
+1];
796 // use buffer to set block size first
797 snprintf(hash_set
,HASH_HEADER_SIZE
,"%ld",blocksize
);
798 apr_table_set(r
->headers_in
, "Block-Size", hash_set
);
800 uint32_t crcs
[BLOCK_COUNT
+1];
801 crc_of_blocks(data
, len
, blocksize
, 30, crcs
);
803 for (i
= 0; i
< BLOCK_COUNT
;++i
)
805 // encode the hase into base64
806 encode_30bithash(crcs
[i
],&hash_set
[i
*HASH_BASE64_SIZE_TX
]);
807 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
808 "crccache: block %d, hash %08X",i
,crcs
[i
]);
810 apr_bucket_delete(e
);
812 // TODO: do we want to cache the hashes here?
813 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "adding block-hashes header: %s",hash_set
);
814 apr_table_set(r
->headers_in
, "Block-Hashes", hash_set
);
816 crccache_client_ctx
* ctx
;
817 ctx
= apr_pcalloc(r
->pool
, sizeof(*ctx
));
818 ctx
->bb
= apr_brigade_create(r
->pool
, r
->connection
->bucket_alloc
);
819 ctx
->block_size
= blocksize
;
820 ctx
->state
= DECODING_NEW_SECTION
;
822 // we want to add a filter here so that we can decode the response.
823 // we need access to the original cached data when we get the response as
824 // we need that to fill in the matched blocks.
825 ap_add_output_filter_handle(crccache_decode_filter_handle
,
826 ctx
, r
, r
->connection
);
828 apr_file_close(dobj
->hfd
);
830 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
831 "disk_cache: Recalled headers for URL %s", dobj
->name
);
835 static apr_status_t
recall_body(cache_handle_t
*h
, apr_pool_t
*p
,
836 apr_bucket_brigade
*bb
) {
838 disk_cache_object_t
*dobj
= (disk_cache_object_t
*) h
->cache_obj
->vobj
;
840 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,"recall_body" );
842 e
= apr_bucket_file_create(dobj
->fd
, 0, (apr_size_t
) dobj
->file_size
, p
,
845 APR_BRIGADE_INSERT_HEAD(bb
, e
);
846 e
= apr_bucket_eos_create(bb
->bucket_alloc
);
847 APR_BRIGADE_INSERT_TAIL(bb
, e
);
852 static apr_status_t
store_table(apr_file_t
*fd
, apr_table_t
*table
) {
857 apr_table_entry_t
*elts
;
859 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, NULL
,"store_table" );
861 elts
= (apr_table_entry_t
*) apr_table_elts(table
)->elts
;
862 for (i
= 0; i
< apr_table_elts(table
)->nelts
; ++i
) {
863 if (elts
[i
].key
!= NULL
) {
864 iov
[0].iov_base
= elts
[i
].key
;
865 iov
[0].iov_len
= strlen(elts
[i
].key
);
866 iov
[1].iov_base
= ": ";
867 iov
[1].iov_len
= sizeof(": ") - 1;
868 iov
[2].iov_base
= elts
[i
].val
;
869 iov
[2].iov_len
= strlen(elts
[i
].val
);
870 iov
[3].iov_base
= CRLF
;
871 iov
[3].iov_len
= sizeof(CRLF
) - 1;
873 rv
= apr_file_writev(fd
, (const struct iovec
*) &iov
, 4,
875 if (rv
!= APR_SUCCESS
) {
880 iov
[0].iov_base
= CRLF
;
881 iov
[0].iov_len
= sizeof(CRLF
) - 1;
882 rv
= apr_file_writev(fd
, (const struct iovec
*) &iov
, 1,
887 static apr_status_t
store_headers(cache_handle_t
*h
, request_rec
*r
,
889 disk_cache_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
890 &crccache_client_module
);
891 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "store_headers");
895 disk_cache_object_t
*dobj
= (disk_cache_object_t
*) h
->cache_obj
->vobj
;
897 disk_cache_info_t disk_info
;
900 /* This is flaky... we need to manage the cache_info differently */
901 h
->cache_obj
->info
= *info
;
903 if (r
->headers_out
) {
906 tmp
= apr_table_get(r
->headers_out
, "Vary");
909 apr_array_header_t
* varray
;
910 apr_uint32_t format
= VARY_FORMAT_VERSION
;
912 /* If we were initially opened as a vary format, rollback
913 * that internal state for the moment so we can recreate the
914 * vary format hints in the appropriate directory.
917 dobj
->hdrsfile
= dobj
->prefix
;
921 mkdir_structure(conf
, dobj
->hdrsfile
, r
->pool
);
923 rv
= apr_file_mktemp(&dobj
->tfd
, dobj
->tempfile
,
924 APR_CREATE
| APR_WRITE
| APR_BINARY
| APR_EXCL
,
927 if (rv
!= APR_SUCCESS
) {
931 amt
= sizeof(format
);
932 apr_file_write(dobj
->tfd
, &format
, &amt
);
934 amt
= sizeof(info
->expire
);
935 apr_file_write(dobj
->tfd
, &info
->expire
, &amt
);
937 varray
= apr_array_make(r
->pool
, 6, sizeof(char*));
938 tokens_to_array(r
->pool
, tmp
, varray
);
940 store_array(dobj
->tfd
, varray
);
942 apr_file_close(dobj
->tfd
);
946 rv
= safe_file_rename(conf
, dobj
->tempfile
, dobj
->hdrsfile
,
948 if (rv
!= APR_SUCCESS
) {
949 ap_log_error(APLOG_MARK
, APLOG_WARNING
, rv
, r
->server
,
950 "disk_cache: rename tempfile to varyfile failed: %s -> %s",
951 dobj
->tempfile
, dobj
->hdrsfile
);
952 apr_file_remove(dobj
->tempfile
, r
->pool
);
956 dobj
->tempfile
= apr_pstrcat(r
->pool
, conf
->cache_root
, AP_TEMPFILE
, NULL
);
957 tmp
= regen_key(r
->pool
, r
->headers_in
, varray
, dobj
->name
);
958 dobj
->prefix
= dobj
->hdrsfile
;
959 dobj
->hashfile
= NULL
;
960 dobj
->datafile
= data_file(r
->pool
, conf
, dobj
, tmp
);
961 dobj
->hdrsfile
= header_file(r
->pool
, conf
, dobj
, tmp
);
966 rv
= apr_file_mktemp(&dobj
->hfd
, dobj
->tempfile
,
967 APR_CREATE
| APR_WRITE
| APR_BINARY
|
968 APR_BUFFERED
| APR_EXCL
, r
->pool
);
970 if (rv
!= APR_SUCCESS
) {
974 disk_info
.format
= DISK_FORMAT_VERSION
;
975 disk_info
.date
= info
->date
;
976 disk_info
.expire
= info
->expire
;
977 disk_info
.entity_version
= dobj
->disk_info
.entity_version
++;
978 disk_info
.request_time
= info
->request_time
;
979 disk_info
.response_time
= info
->response_time
;
980 disk_info
.status
= info
->status
;
982 disk_info
.name_len
= strlen(dobj
->name
);
984 iov
[0].iov_base
= (void*)&disk_info
;
985 iov
[0].iov_len
= sizeof(disk_cache_info_t
);
986 iov
[1].iov_base
= (void*)dobj
->name
;
987 iov
[1].iov_len
= disk_info
.name_len
;
989 rv
= apr_file_writev(dobj
->hfd
, (const struct iovec
*) &iov
, 2, &amt
);
990 if (rv
!= APR_SUCCESS
) {
994 if (r
->headers_out
) {
995 apr_table_t
*headers_out
;
997 headers_out
= ap_cache_cacheable_hdrs_out(r
->pool
, r
->headers_out
,
1000 if (!apr_table_get(headers_out
, "Content-Type")
1001 && r
->content_type
) {
1002 apr_table_setn(headers_out
, "Content-Type",
1003 ap_make_content_type(r
, r
->content_type
));
1006 headers_out
= apr_table_overlay(r
->pool
, headers_out
,
1007 r
->err_headers_out
);
1008 rv
= store_table(dobj
->hfd
, headers_out
);
1009 if (rv
!= APR_SUCCESS
) {
1014 /* Parse the vary header and dump those fields from the headers_in. */
1015 /* FIXME: Make call to the same thing cache_select calls to crack Vary. */
1016 if (r
->headers_in
) {
1017 apr_table_t
*headers_in
;
1019 headers_in
= ap_cache_cacheable_hdrs_out(r
->pool
, r
->headers_in
,
1021 rv
= store_table(dobj
->hfd
, headers_in
);
1022 if (rv
!= APR_SUCCESS
) {
1027 apr_file_close(dobj
->hfd
); /* flush and close */
1029 /* Remove old file with the same name. If remove fails, then
1030 * perhaps we need to create the directory tree where we are
1031 * about to write the new headers file.
1033 rv
= apr_file_remove(dobj
->hdrsfile
, r
->pool
);
1034 if (rv
!= APR_SUCCESS
) {
1035 mkdir_structure(conf
, dobj
->hdrsfile
, r
->pool
);
1038 rv
= safe_file_rename(conf
, dobj
->tempfile
, dobj
->hdrsfile
, r
->pool
);
1039 if (rv
!= APR_SUCCESS
) {
1040 ap_log_error(APLOG_MARK
, APLOG_WARNING
, rv
, r
->server
,
1041 "disk_cache: rename tempfile to hdrsfile failed: %s -> %s",
1042 dobj
->tempfile
, dobj
->hdrsfile
);
1043 apr_file_remove(dobj
->tempfile
, r
->pool
);
1047 dobj
->tempfile
= apr_pstrcat(r
->pool
, conf
->cache_root
, AP_TEMPFILE
, NULL
);
1049 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
1050 "disk_cache: Stored headers for URL %s", dobj
->name
);
1054 static apr_status_t
store_body(cache_handle_t
*h
, request_rec
*r
,
1055 apr_bucket_brigade
*bb
) {
1059 disk_cache_object_t
*dobj
= (disk_cache_object_t
*) h
->cache_obj
->vobj
;
1060 disk_cache_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
1061 &crccache_client_module
);
1063 /* We write to a temp file and then atomically rename the file over
1064 * in file_cache_el_final().
1067 rv
= apr_file_mktemp(&dobj
->tfd
, dobj
->tempfile
, APR_CREATE
| APR_WRITE
1068 | APR_BINARY
| APR_BUFFERED
| APR_EXCL
, r
->pool
);
1069 if (rv
!= APR_SUCCESS
) {
1072 dobj
->file_size
= 0;
1075 for (e
= APR_BRIGADE_FIRST(bb
); e
!= APR_BRIGADE_SENTINEL(bb
); e
= APR_BUCKET_NEXT(e
)) {
1077 apr_size_t length
, written
;
1078 rv
= apr_bucket_read(e
, &str
, &length
, APR_BLOCK_READ
);
1079 if (rv
!= APR_SUCCESS
) {
1080 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,
1081 "cache_disk: Error when reading bucket for URL %s",
1083 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1084 file_cache_errorcleanup(dobj
, r
);
1087 rv
= apr_file_write_full(dobj
->tfd
, str
, length
, &written
);
1088 if (rv
!= APR_SUCCESS
) {
1089 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,
1090 "cache_disk: Error when writing cache file for URL %s",
1092 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1093 file_cache_errorcleanup(dobj
, r
);
1096 dobj
->file_size
+= written
;
1097 if (dobj
->file_size
> conf
->maxfs
) {
1098 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
1099 "cache_disk: URL %s failed the size check "
1100 "(%" APR_OFF_T_FMT
" > %" APR_OFF_T_FMT
")",
1101 h
->cache_obj
->key
, dobj
->file_size
, conf
->maxfs
);
1102 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1103 file_cache_errorcleanup(dobj
, r
);
1104 return APR_EGENERAL
;
1108 /* Was this the final bucket? If yes, close the temp file and perform
1111 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb
))) {
1112 if (r
->connection
->aborted
|| r
->no_cache
) {
1113 ap_log_error(APLOG_MARK
, APLOG_INFO
, 0, r
->server
,
1114 "disk_cache: Discarding body for URL %s "
1115 "because connection has been aborted.",
1117 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1118 file_cache_errorcleanup(dobj
, r
);
1119 return APR_EGENERAL
;
1121 if (dobj
->file_size
< conf
->minfs
) {
1122 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
1123 "cache_disk: URL %s failed the size check "
1124 "(%" APR_OFF_T_FMT
" < %" APR_OFF_T_FMT
")",
1125 h
->cache_obj
->key
, dobj
->file_size
, conf
->minfs
);
1126 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1127 file_cache_errorcleanup(dobj
, r
);
1128 return APR_EGENERAL
;
1131 /* All checks were fine. Move tempfile to final destination */
1132 /* Link to the perm file, and close the descriptor */
1133 file_cache_el_final(dobj
, r
);
1134 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
,
1135 "disk_cache: Body for URL %s cached.", dobj
->name
);
1142 * CACHE_DECODE filter
1145 * Deliver cached content (headers and body) up the stack.
1147 static int crccache_decode_filter(ap_filter_t
*f
, apr_bucket_brigade
*bb
) {
1149 request_rec
*r
= f
->r
;
1150 // TODO: set up context type struct
1151 crccache_client_ctx
*ctx
= f
->ctx
;
1153 // TODO: make this work if we have multiple encodings
1154 const char * content_encoding
;
1155 content_encoding
= apr_table_get(r
->headers_out
, "Content-Encoding");
1156 if (content_encoding
== NULL
|| strcmp(CRCCACHE_ENCODING
, content_encoding
)
1158 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1159 "CRCSYNC not decoding, content encoding bad (%s)", content_encoding
?content_encoding
:"NULL");
1160 ap_remove_output_filter(f
);
1161 return ap_pass_brigade(f
->next
, bb
);
1163 // TODO: Remove crcsync from the content encoding header
1165 // TODO: Fix up the etag as well
1167 /* Do nothing if asked to filter nothing. */
1168 if (APR_BRIGADE_EMPTY(bb
)) {
1169 return ap_pass_brigade(f
->next
, bb
);
1172 /* We require that we have a context already, otherwise we dont have our cached file
1173 * to fill in the gaps with.
1176 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1177 "No context availavle %s", r
->uri
);
1178 ap_remove_output_filter(f
);
1179 return ap_pass_brigade(f
->next
, bb
);
1182 while (!APR_BRIGADE_EMPTY(bb
))
1188 e
= APR_BRIGADE_FIRST(bb
);
1190 if (APR_BUCKET_IS_EOS(e
)) {
1192 /* Remove EOS from the old list, and insert into the new. */
1193 APR_BUCKET_REMOVE(e
);
1194 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1196 /* This filter is done once it has served up its content */
1197 ap_remove_output_filter(f
);
1199 /* Okay, we've seen the EOS.
1200 * Time to pass it along down the chain.
1202 return ap_pass_brigade(f
->next
, ctx
->bb
);
1205 if (APR_BUCKET_IS_FLUSH(e
)) {
1208 /* Remove flush bucket from old brigade anf insert into the new. */
1209 APR_BUCKET_REMOVE(e
);
1210 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1211 rv
= ap_pass_brigade(f
->next
, ctx
->bb
);
1212 if (rv
!= APR_SUCCESS
) {
1218 if (APR_BUCKET_IS_METADATA(e
)) {
1220 * Remove meta data bucket from old brigade and insert into the
1223 APR_BUCKET_REMOVE(e
);
1224 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1229 apr_bucket_read(e
, &data
, &len
, APR_BLOCK_READ
);
1231 size_t consumed_bytes
= 0;
1232 while (consumed_bytes
< len
)
1234 // no guaruntee that our buckets line up with our encoding sections
1235 // so we need a processing state machine stored in our context
1238 case DECODING_NEW_SECTION
:
1239 ctx
->processed_length
= 0;
1241 // check if we have a literal section or a block section
1242 if (data
[consumed_bytes
] == ENCODING_LITERAL
)
1243 ctx
->state
= DECODING_LITERAL_HEADER
;
1244 else if (data
[consumed_bytes
] == ENCODING_BLOCK
)
1245 ctx
->state
= DECODING_BLOCK_HEADER
;
1247 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1248 "CRCSYNC-DECODE, unknown section %d(%c)",data
[consumed_bytes
],data
[consumed_bytes
]);
1250 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1251 "CRCSYNC-DECODE, found a new section %d",ctx
->state
);
1254 case DECODING_LITERAL_HEADER
:
1255 // how long a literal do we have
1256 if (ctx
->processed_length
== 0 && (len
-consumed_bytes
)>=4)
1258 ctx
->section_length
= ntohl(*(unsigned long*)&data
[consumed_bytes
]);
1260 ctx
->state
= DECODING_LITERAL
;
1261 ctx
->processed_length
= 0;
1262 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1263 "CRCSYNC-DECODE, lit section, length %d",ctx
->section_length
);
1268 // we didnt get the whole length in one go, so assemble it in our ctx buffer
1269 int remaining_header_count
= MIN(len
- consumed_bytes
, 4 - ctx
->processed_length
);
1270 memcpy(&ctx
->section_header
[ctx
->processed_length
],&data
[consumed_bytes
],remaining_header_count
);
1271 ctx
->processed_length
+= remaining_header_count
;
1272 consumed_bytes
+= remaining_header_count
;
1273 if (ctx
->processed_length
== 4)
1275 ctx
->section_length
= ntohl(*(unsigned long*)&data
[consumed_bytes
]);
1276 ctx
->state
= DECODING_LITERAL
;
1277 ctx
->processed_length
= 0;
1278 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1279 "CRCSYNC-DECODE, literal section, lenfth %d",ctx
->section_length
);
1284 case DECODING_BLOCK_HEADER
:
1286 unsigned char block_number
= data
[consumed_bytes
];
1288 ctx
->state
= DECODING_NEW_SECTION
;
1290 // TODO: Output the indicated block here
1291 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1292 "CRCSYNC-DECODE, block section, block %d",block_number
);
1294 char * buf
= apr_palloc(r
->pool
, ctx
->block_size
);
1295 memset(buf
,'B',ctx
->block_size
);
1296 apr_bucket
* b
= apr_bucket_pool_create(buf
, ctx
->block_size
, r
->pool
, f
->c
->bucket_alloc
);
1297 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, b
);
1301 case DECODING_LITERAL
:
1303 unsigned literal_data_len
= MIN(len
- consumed_bytes
, ctx
->section_length
- ctx
->processed_length
);
1304 // TODO dump literal data here
1305 char * buf
= apr_palloc(r
->pool
, literal_data_len
);
1306 memcpy(buf
,&data
[consumed_bytes
],literal_data_len
);
1307 apr_bucket
* b
= apr_bucket_pool_create(buf
, literal_data_len
, r
->pool
, f
->c
->bucket_alloc
);
1308 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, b
);
1310 consumed_bytes
+= literal_data_len
;
1311 ctx
->processed_length
+= literal_data_len
;
1312 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1313 "CRCSYNC-DECODE, lit, bytes %d, %d %d",literal_data_len
, ctx
->processed_length
, ctx
->section_length
);
1315 if (ctx
->processed_length
== ctx
->section_length
)
1317 ctx
->state
= DECODING_NEW_SECTION
;
1323 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1324 "CRCSYNC-DECODE, unknown state %d, terminating transaction",ctx
->state
);
1325 apr_brigade_cleanup(bb
);
1329 APR_BUCKET_REMOVE(e
);
1333 apr_brigade_cleanup(bb
);
1337 static void *create_config(apr_pool_t
*p
, server_rec
*s
) {
1338 disk_cache_conf
*conf
= apr_pcalloc(p
, sizeof(disk_cache_conf
));
1340 /* XXX: Set default values */
1341 conf
->dirlevels
= DEFAULT_DIRLEVELS
;
1342 conf
->dirlength
= DEFAULT_DIRLENGTH
;
1343 conf
->maxfs
= DEFAULT_MAX_FILE_SIZE
;
1344 conf
->minfs
= DEFAULT_MIN_FILE_SIZE
;
1346 conf
->cache_root
= NULL
;
1347 conf
->cache_root_len
= 0;
1353 * mod_disk_cache configuration directives handlers.
1355 static const char *set_cache_root(cmd_parms
*parms
, void *in_struct_ptr
,
1357 disk_cache_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
1358 &crccache_client_module
);
1359 conf
->cache_root
= arg
;
1360 conf
->cache_root_len
= strlen(arg
);
1361 /* TODO: canonicalize cache_root and strip off any trailing slashes */
1367 * Consider eliminating the next two directives in favor of
1368 * Ian's prime number hash...
1369 * key = hash_fn( r->uri)
1370 * filename = "/key % prime1 /key %prime2/key %prime3"
1372 static const char *set_cache_dirlevels(cmd_parms
*parms
, void *in_struct_ptr
,
1374 disk_cache_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
1375 &crccache_client_module
);
1376 int val
= atoi(arg
);
1378 return "CacheDirLevels value must be an integer greater than 0";
1379 if (val
* conf
->dirlength
> CACHEFILE_LEN
)
1380 return "CacheDirLevels*CacheDirLength value must not be higher than 20";
1381 conf
->dirlevels
= val
;
1384 static const char *set_cache_dirlength(cmd_parms
*parms
, void *in_struct_ptr
,
1386 disk_cache_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
1387 &crccache_client_module
);
1388 int val
= atoi(arg
);
1390 return "CacheDirLength value must be an integer greater than 0";
1391 if (val
* conf
->dirlevels
> CACHEFILE_LEN
)
1392 return "CacheDirLevels*CacheDirLength value must not be higher than 20";
1394 conf
->dirlength
= val
;
1398 static const char *set_cache_minfs(cmd_parms
*parms
, void *in_struct_ptr
,
1400 disk_cache_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
1401 &crccache_client_module
);
1403 if (apr_strtoff(&conf
->minfs
, arg
, NULL
, 0) != APR_SUCCESS
|| conf
->minfs
1405 return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes.";
1410 static const char *set_cache_maxfs(cmd_parms
*parms
, void *in_struct_ptr
,
1412 disk_cache_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
1413 &crccache_client_module
);
1414 if (apr_strtoff(&conf
->maxfs
, arg
, NULL
, 0) != APR_SUCCESS
|| conf
->maxfs
1416 return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes.";
1421 static const command_rec disk_cache_cmds
[] = { AP_INIT_TAKE1("CacheRoot", set_cache_root
, NULL
, RSRC_CONF
,
1422 "The directory to store cache files"), AP_INIT_TAKE1("CacheDirLevels", set_cache_dirlevels
, NULL
, RSRC_CONF
,
1423 "The number of levels of subdirectories in the cache"), AP_INIT_TAKE1("CacheDirLength", set_cache_dirlength
, NULL
, RSRC_CONF
,
1424 "The number of characters in subdirectory names"), AP_INIT_TAKE1("CacheMinFileSize", set_cache_minfs
, NULL
, RSRC_CONF
,
1425 "The minimum file size to cache a document"), AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs
, NULL
, RSRC_CONF
,
1426 "The maximum file size to cache a document"), { NULL
} };
1428 static const cache_provider crccache_client_provider
= { &remove_entity
,
1429 &store_headers
, &store_body
, &recall_headers
, &recall_body
,
1430 &create_entity
, &open_entity
, &remove_url
, };
1432 static void disk_cache_register_hook(apr_pool_t
*p
) {
1433 /* cache initializer */
1434 ap_register_provider(p
, CACHE_PROVIDER_GROUP
, "crccache_client", "0",
1435 &crccache_client_provider
);
1437 * CACHE_OUT must go into the filter chain after a possible DEFLATE
1438 * filter to ensure that already compressed cache objects do not
1439 * get compressed again. Incrementing filter type by 1 ensures
1442 crccache_decode_filter_handle
= ap_register_output_filter(
1443 "CRCCACHE_DECODE", crccache_decode_filter
, NULL
,
1444 AP_FTYPE_CONTENT_SET
+ 1);
1447 module AP_MODULE_DECLARE_DATA crccache_client_module
= {
1448 STANDARD20_MODULE_STUFF
, NULL
, /* create per-directory config structure */
1449 NULL
, /* merge per-directory config structures */
1450 create_config
, /* create per-server config structure */
1451 NULL
, /* merge per-server config structures */
1452 disk_cache_cmds
, /* command apr_table_t */
1453 disk_cache_register_hook
/* register hooks */