diff options
Diffstat (limited to 'modules/cache')
27 files changed, 8151 insertions, 0 deletions
diff --git a/modules/cache/.indent.pro b/modules/cache/.indent.pro new file mode 100644 index 00000000..a9fbe9f9 --- /dev/null +++ b/modules/cache/.indent.pro @@ -0,0 +1,54 @@ +-i4 -npsl -di0 -br -nce -d0 -cli0 -npcs -nfc1 +-TBUFF +-TFILE +-TTRANS +-TUINT4 +-T_trans +-Tallow_options_t +-Tapache_sfio +-Tarray_header +-Tbool_int +-Tbuf_area +-Tbuff_struct +-Tbuffy +-Tcmd_how +-Tcmd_parms +-Tcommand_rec +-Tcommand_struct +-Tconn_rec +-Tcore_dir_config +-Tcore_server_config +-Tdir_maker_func +-Tevent +-Tglobals_s +-Thandler_func +-Thandler_rec +-Tjoblist_s +-Tlisten_rec +-Tmerger_func +-Tmode_t +-Tmodule +-Tmodule_struct +-Tmutex +-Tn_long +-Tother_child_rec +-Toverrides_t +-Tparent_score +-Tpid_t +-Tpiped_log +-Tpool +-Trequest_rec +-Trequire_line +-Trlim_t +-Tscoreboard +-Tsemaphore +-Tserver_addr_rec +-Tserver_rec +-Tserver_rec_chain +-Tshort_score +-Ttable +-Ttable_entry +-Tthread +-Tu_wide_int +-Tvtime_t +-Twide_int diff --git a/modules/cache/Makefile.in b/modules/cache/Makefile.in new file mode 100644 index 00000000..167b343d --- /dev/null +++ b/modules/cache/Makefile.in @@ -0,0 +1,3 @@ + +include $(top_srcdir)/build/special.mk + diff --git a/modules/cache/NWGNUdsk_cach b/modules/cache/NWGNUdsk_cach new file mode 100644 index 00000000..9dc385e7 --- /dev/null +++ b/modules/cache/NWGNUdsk_cach @@ -0,0 +1,266 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\build\NWGNUhead.inc + +# +# build this level's files +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(AP_WORK)/include \ + $(AP_WORK)/os/NetWare \ + $(AP_WORK)/server/mpm/NetWare \ + $(AP_WORK)/srclib/pcre \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = dsk_cach + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Memory Cache Sub-Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = dsk_cach + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\build\NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 65536 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _LibCPrelude + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _LibCPostlude + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/dsk_cach.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_disk_cache.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + libcpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + Apache2 \ + Libc \ + mod_cach \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @$(APR)/aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + disk_cache_module \ + $(EOLIST) + +# @cache.imp \ +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\build\NWGNUtail.inc + + + diff --git a/modules/cache/NWGNUmakefile b/modules/cache/NWGNUmakefile new file mode 100644 index 00000000..71d7e1ba --- /dev/null +++ b/modules/cache/NWGNUmakefile @@ -0,0 +1,246 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\build\NWGNUhead.inc + +# +# build this level's files + +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\build\NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If these are specified it will be used by the link '-flags' directive +# +NLM_FLAGS = + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/mod_cach.nlm \ + $(OBJDIR)/mem_cach.nlm \ + $(OBJDIR)/dsk_cach.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples) +# +install :: nlms FORCE + copy $(OBJDIR)\*.nlm $(INSTALL)\Apache2\modules\*.* + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\build\NWGNUtail.inc + diff --git a/modules/cache/NWGNUmem_cach b/modules/cache/NWGNUmem_cach new file mode 100644 index 00000000..782d8834 --- /dev/null +++ b/modules/cache/NWGNUmem_cach @@ -0,0 +1,270 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\build\NWGNUhead.inc + +# +# build this level's files +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(AP_WORK)/include \ + $(AP_WORK)/os/NetWare \ + $(AP_WORK)/server/mpm/NetWare \ + $(AP_WORK)/srclib/pcre \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + -DDEBUG \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = mem_cach + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Memory Cache Sub-Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = mem_cach + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\build\NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 65536 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _LibCPrelude + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _LibCPostlude + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/mem_cach.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/mod_mem_cache.o \ + $(OBJDIR)/cache_hash.o \ + $(OBJDIR)/cache_pqueue.o \ + $(OBJDIR)/cache_cache.o \ + $(EOLIST) + +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + libcpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + Apache2 \ + Libc \ + mod_cach \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @$(APR)/aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + mem_cache_module \ + $(EOLIST) + +# @cache.imp \ +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\build\NWGNUtail.inc + + + diff --git a/modules/cache/NWGNUmod_cach b/modules/cache/NWGNUmod_cach new file mode 100644 index 00000000..125c4567 --- /dev/null +++ b/modules/cache/NWGNUmod_cach @@ -0,0 +1,269 @@ +# +# Declare the sub-directories to be built here +# + +SUBDIRS = \ + $(EOLIST) + +# +# Get the 'head' of the build environment. This includes default targets and +# paths to tools +# + +include $(AP_WORK)\build\NWGNUhead.inc + +# +# build this level's files +# +# Make sure all needed macro's are defined +# + +# +# These directories will be at the beginning of the include list, followed by +# INCDIRS +# +XINCDIRS += \ + $(APR)/include \ + $(APRUTIL)/include \ + $(AP_WORK)/include \ + $(AP_WORK)/os/NetWare \ + $(AP_WORK)/server/mpm/NetWare \ + $(AP_WORK)/srclib/pcre \ + $(NWOS) \ + $(EOLIST) + +# +# These flags will come after CFLAGS +# +XCFLAGS += \ + $(EOLIST) + +# +# These defines will come after DEFINES +# +XDEFINES += \ + $(EOLIST) + +# +# These flags will be added to the link.opt file +# +XLFLAGS += \ + $(EOLIST) + +# +# These values will be appended to the correct variables based on the value of +# RELEASE +# +ifeq "$(RELEASE)" "debug" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + -DDEBUG \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "noopt" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +ifeq "$(RELEASE)" "release" +XINCDIRS += \ + $(EOLIST) + +XCFLAGS += \ + $(EOLIST) + +XDEFINES += \ + $(EOLIST) + +XLFLAGS += \ + $(EOLIST) +endif + +# +# These are used by the link target if an NLM is being generated +# This is used by the link 'name' directive to name the nlm. If left blank +# TARGET_nlm (see below) will be used. +# +NLM_NAME = mod_cach + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Cache module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = mod_cach + +# +# If this is specified, it will override VERSION value in +# $(AP_WORK)\build\NWGNUenvironment.inc +# +NLM_VERSION = + +# +# If this is specified, it will override the default of 64K +# +NLM_STACK_SIZE = 65536 + + +# +# If this is specified it will be used by the link '-entry' directive +# +NLM_ENTRY_SYM = _LibCPrelude + +# +# If this is specified it will be used by the link '-exit' directive +# +NLM_EXIT_SYM = _LibCPostlude + +# +# If this is specified it will be used by the link '-check' directive +# +NLM_CHECK_SYM = + +# +# If this is specified it will be used by the link '-flags' directive +# +NLM_FLAGS = AUTOUNLOAD, PSEUDOPREEMPTION + +# +# If this is specified it will be linked in with the XDCData option in the def +# file instead of the default of $(NWOS)/apache.xdc. XDCData can be disabled +# by setting APACHE_UNIPROC in the environment +# +XDCDATA = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/mod_cach.nlm \ + $(EOLIST) + +# +# If there is an LIB target, put it here +# +TARGET_lib = \ + $(EOLIST) + +# +# These are the OBJ files needed to create the NLM target above. +# Paths must all use the '/' character +# +FILES_nlm_objs = \ + $(OBJDIR)/cache_util.o \ + $(OBJDIR)/cache_storage.o \ + $(OBJDIR)/mod_cache.o \ + $(EOLIST) + +# $(OBJDIR)/mod_mem_cache.o \ +# +# These are the LIB files needed to create the NLM target above. +# These will be added as a library command in the link.opt file. +# +FILES_nlm_libs = \ + libcpre.o \ + $(EOLIST) + +# +# These are the modules that the above NLM target depends on to load. +# These will be added as a module command in the link.opt file. +# +FILES_nlm_modules = \ + Apache2 \ + Libc \ + $(EOLIST) + +# +# If the nlm has a msg file, put it's path here +# +FILE_nlm_msg = + +# +# If the nlm has a hlp file put it's path here +# +FILE_nlm_hlp = + +# +# If this is specified, it will override $(NWOS)\copyright.txt. +# +FILE_nlm_copyright = + +# +# Any additional imports go here +# +FILES_nlm_Ximports = \ + @libc.imp \ + @$(APR)/aprlib.imp \ + @httpd.imp \ + @netware.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + @mod_cache.imp \ + cache_module \ + $(EOLIST) + +# +# These are the OBJ files needed to create the LIB target above. +# Paths must all use the '/' character +# +FILES_lib_objs = \ + $(EOLIST) + +# +# implement targets and dependancies (leave this section alone) +# + +libs :: $(OBJDIR) $(TARGET_lib) + +nlms :: libs $(TARGET_nlm) + +# +# Updated this target to create necessary directories and copy files to the +# correct place. (See $(AP_WORK)\build\NWGNUhead.inc for examples) +# +install :: nlms FORCE + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(AP_WORK)\build\NWGNUtail.inc + + + diff --git a/modules/cache/cache_cache.c b/modules/cache/cache_cache.c new file mode 100644 index 00000000..6db98f71 --- /dev/null +++ b/modules/cache/cache_cache.c @@ -0,0 +1,171 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_general.h" + +#include "mod_cache.h" +#include "cache_hash.h" +#include "cache_pqueue.h" +#include "cache_cache.h" + +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if APR_HAVE_STRING_H +#include <string.h> +#endif + +struct cache_cache_t { + int max_entries; + apr_size_t max_size; + apr_size_t current_size; + int total_purges; + long queue_clock; + cache_hash_t *ht; + cache_pqueue_t *pq; + cache_pqueue_set_priority set_pri; + cache_pqueue_get_priority get_pri; + cache_cache_inc_frequency *inc_entry; + cache_cache_get_size *size_entry; + cache_cache_get_key *key_entry; + cache_cache_free *free_entry; +}; + +CACHE_DECLARE(cache_cache_t *)cache_init(int max_entries, + apr_size_t max_size, + cache_pqueue_get_priority get_pri, + cache_pqueue_set_priority set_pri, + cache_pqueue_getpos get_pos, + cache_pqueue_setpos set_pos, + cache_cache_inc_frequency *inc_entry, + cache_cache_get_size *size_entry, + cache_cache_get_key* key_entry, + cache_cache_free *free_entry) +{ + cache_cache_t *tmp; + tmp = malloc(sizeof(cache_cache_t)); + tmp->max_entries = max_entries; + tmp->max_size = max_size; + tmp->current_size = 0; + tmp->total_purges = 0; + tmp->queue_clock = 0; + tmp->get_pri = get_pri; + tmp->set_pri = set_pri; + tmp->inc_entry = inc_entry; + tmp->size_entry = size_entry; + tmp->key_entry = key_entry; + tmp->free_entry = free_entry; + + tmp->ht = cache_hash_make(max_entries); + tmp->pq = cache_pq_init(max_entries, get_pri, get_pos, set_pos); + + return tmp; +} + +CACHE_DECLARE(void) cache_free(cache_cache_t *c) +{ + cache_pq_free(c->pq); + cache_hash_free(c->ht); + free(c); +} + + +CACHE_DECLARE(void*) cache_find(cache_cache_t* c, const char *key) +{ + void *e; + + e = cache_hash_get(c->ht, key, CACHE_HASH_KEY_STRING); + if (!e) + return NULL; + + return e; +} + +CACHE_DECLARE(void) cache_update(cache_cache_t* c, void *entry) +{ + long old_priority; + long new_priority; + + old_priority = c->set_pri(c->queue_clock, entry); + c->inc_entry(entry); + new_priority = c->set_pri(c->queue_clock, entry); + cache_pq_change_priority(c->pq, old_priority, new_priority, entry); +} + +CACHE_DECLARE(void) cache_insert(cache_cache_t* c, void *entry) +{ + void *ejected = NULL; + long priority; + + c->set_pri(c->queue_clock, entry); + /* FIX: check if priority of bottom item is greater than inserted one */ + while ((cache_pq_size(c->pq) >= c->max_entries) || + ((c->current_size + c->size_entry(entry)) > c->max_size)) { + + ejected = cache_pq_pop(c->pq); + /* FIX: If ejected is NULL, we'll segfault here */ + priority = c->get_pri(ejected); + + if (c->queue_clock > priority) + c->queue_clock = priority; + + cache_hash_set(c->ht, + c->key_entry(ejected), + CACHE_HASH_KEY_STRING, + NULL); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "Cache Purge of %s",c->key_entry(ejected)); + c->current_size -= c->size_entry(ejected); + c->free_entry(ejected); + c->total_purges++; + } + c->current_size += c->size_entry(entry); + + cache_pq_insert(c->pq, entry); + cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, entry); +} + +CACHE_DECLARE(void *) cache_pop(cache_cache_t *c) +{ + void *entry; + + if (!c) + return NULL; + + entry = cache_pq_pop(c->pq); + + if (!entry) + return NULL; + + c->current_size -= c->size_entry(entry); + cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, NULL); + + return entry; +} + +CACHE_DECLARE(apr_status_t) cache_remove(cache_cache_t *c, void *entry) +{ + apr_size_t entry_size = c->size_entry(entry); + apr_status_t rc; + rc = cache_pq_remove(c->pq, entry); + if (rc != APR_SUCCESS) + return rc; + + cache_hash_set(c->ht, c->key_entry(entry), CACHE_HASH_KEY_STRING, NULL); + c->current_size -= entry_size; + + return APR_SUCCESS; +} diff --git a/modules/cache/cache_cache.h b/modules/cache/cache_cache.h new file mode 100644 index 00000000..042c5d50 --- /dev/null +++ b/modules/cache/cache_cache.h @@ -0,0 +1,111 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file cache_cache.h + * @brief Cache Cache Functions + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_CACHE_H +#define CACHE_CACHE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mod_cache.h" + +/** ADT for the cache */ +typedef struct cache_cache_t cache_cache_t; + +/** callback to increment the frequency of a item */ +typedef void cache_cache_inc_frequency(void*a); +/** callback to get the size of a item */ +typedef apr_size_t cache_cache_get_size(void*a); +/** callback to get the key of a item */ +typedef const char* cache_cache_get_key(void *a); +/** callback to free an entry */ +typedef void cache_cache_free(void *a); + +/** + * initialize the cache ADT + * @param max_entries the number of entries in the cache + * @param max_size the size of the cache + * @param get_pri callback to get a priority of a entry + * @param set_pri callback to set a priority of a entry + * @param get_pos callback to get the position of a entry in the cache + * @param set_pos callback to set the position of a entry in the cache + * @param inc_entry callback to increment the frequency of a entry + * @param size_entry callback to get the size of a entry + * @param key_entry callback to get the key of a entry + * @param free_entry callback to free an entry + */ +CACHE_DECLARE(cache_cache_t *)cache_init(int max_entries, + apr_size_t max_size, + cache_pqueue_get_priority get_pri, + cache_pqueue_set_priority set_pri, + cache_pqueue_getpos get_pos, + cache_pqueue_setpos set_pos, + cache_cache_inc_frequency *inc_entry, + cache_cache_get_size *size_entry, + cache_cache_get_key *key_entry, + cache_cache_free *free_entry); + +/** + * free up the cache + * @param c the cache + */ +CACHE_DECLARE(void) cache_free(cache_cache_t *c); +/** + * find a entry in the cache, incrementing the frequency if found + * @param c the cache + * @param key the key + */ +CACHE_DECLARE(void*) cache_find(cache_cache_t* c, const char *key); +/** + * insert a entry into the cache + * @param c the cache + * @param entry the entry + */ +CACHE_DECLARE(void) cache_update(cache_cache_t* c, void *entry); +/** + * insert a entry into the cache + * @param c the cache + * @param entry the entry + */ +CACHE_DECLARE(void) cache_insert(cache_cache_t* c, void *entry); +/** + * pop the lowest priority item off + * @param c the cache + * @returns the entry or NULL + */ +CACHE_DECLARE(void *)cache_pop(cache_cache_t* c); +/** + * remove an item from the cache + * @param c the cache + * @param entry the actual entry (from a find) + */ +CACHE_DECLARE(apr_status_t) cache_remove(cache_cache_t* c, void *entry); +#ifdef __cplusplus +} +#endif + +#endif /* !CACHE_CACHE_H */ +/** @} */ diff --git a/modules/cache/cache_hash.c b/modules/cache/cache_hash.c new file mode 100644 index 00000000..2ac26ec8 --- /dev/null +++ b/modules/cache/cache_hash.c @@ -0,0 +1,290 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_general.h" + +#include "mod_cache.h" +#include "cache_hash.h" + +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if APR_HAVE_STRING_H +#include <string.h> +#endif + + +/* + * The internal form of a hash table. + * + * The table is an array indexed by the hash of the key; collisions + * are resolved by hanging a linked list of hash entries off each + * element of the array. Although this is a really simple design it + * isn't too bad given that pools have a low allocation overhead. + */ + +typedef struct cache_hash_entry_t cache_hash_entry_t; + +struct cache_hash_entry_t { + cache_hash_entry_t *next; + unsigned int hash; + const void *key; + apr_ssize_t klen; + const void *val; +}; + +/* + * Data structure for iterating through a hash table. + * + * We keep a pointer to the next hash entry here to allow the current + * hash entry to be freed or otherwise mangled between calls to + * cache_hash_next(). + */ +struct cache_hash_index_t { + cache_hash_t *ht; + cache_hash_entry_t *this, *next; + int index; +}; + +/* + * The size of the array is always a power of two. We use the maximum + * index rather than the size so that we can use bitwise-AND for + * modular arithmetic. + * The count of hash entries may be greater depending on the chosen + * collision rate. + */ +struct cache_hash_t { + cache_hash_entry_t **array; + cache_hash_index_t iterator; /* For cache_hash_first(NULL, ...) */ + int count, max; +}; + +/* + * Hash creation functions. + */ +static cache_hash_entry_t **alloc_array(cache_hash_t *ht, int max) +{ + return calloc(1, sizeof(*ht->array) * (max + 1)); +} + +CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size) +{ + cache_hash_t *ht; + ht = malloc(sizeof(cache_hash_t)); + if (!ht) { + return NULL; + } + ht->count = 0; + ht->max = size; + ht->array = alloc_array(ht, ht->max); + if (!ht->array) { + free(ht); + return NULL; + } + return ht; +} + +CACHE_DECLARE(void) cache_hash_free(cache_hash_t *ht) +{ + if (ht) { + if (ht->array) { + free (ht->array); + } + free (ht); + } +} +/* + * Hash iteration functions. + */ + +CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi) +{ + hi->this = hi->next; + while (!hi->this) { + if (hi->index > hi->ht->max) + return NULL; + hi->this = hi->ht->array[hi->index++]; + } + hi->next = hi->this->next; + return hi; +} + +CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht) +{ + cache_hash_index_t *hi; + + hi = &ht->iterator; + hi->ht = ht; + hi->index = 0; + hi->this = NULL; + hi->next = NULL; + return cache_hash_next(hi); +} + +CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi, + const void **key, + apr_ssize_t *klen, + void **val) +{ + if (key) *key = hi->this->key; + if (klen) *klen = hi->this->klen; + if (val) *val = (void *)hi->this->val; +} + + +/* + * This is where we keep the details of the hash function and control + * the maximum collision rate. + * + * If val is non-NULL it creates and initializes a new hash entry if + * there isn't already one there; it returns an updatable pointer so + * that hash entries can be removed. + */ + +static cache_hash_entry_t **find_entry(cache_hash_t *ht, + const void *key, + apr_ssize_t klen, + const void *val) +{ + cache_hash_entry_t **hep, *he; + const unsigned char *p; + unsigned int hash; + apr_ssize_t i; + + /* + * This is the popular `times 33' hash algorithm which is used by + * perl and also appears in Berkeley DB. This is one of the best + * known hash functions for strings because it is both computed + * very fast and distributes very well. + * + * The originator may be Dan Bernstein but the code in Berkeley DB + * cites Chris Torek as the source. The best citation I have found + * is "Chris Torek, Hash function for text in C, Usenet message + * <27038@mimsy.umd.edu> in comp.lang.c , October, 1990." in Rich + * Salz's USENIX 1992 paper about INN which can be found at + * <http://citeseer.nj.nec.com/salz92internetnews.html>. + * + * The magic of number 33, i.e. why it works better than many other + * constants, prime or not, has never been adequately explained by + * anyone. So I try an explanation: if one experimentally tests all + * multipliers between 1 and 256 (as I did while writing a low-level + * data structure library some time ago) one detects that even + * numbers are not useable at all. The remaining 128 odd numbers + * (except for the number 1) work more or less all equally well. + * They all distribute in an acceptable way and this way fill a hash + * table with an average percent of approx. 86%. + * + * If one compares the chi^2 values of the variants (see + * Bob Jenkins ``Hashing Frequently Asked Questions'' at + * http://burtleburtle.net/bob/hash/hashfaq.html for a description + * of chi^2), the number 33 not even has the best value. But the + * number 33 and a few other equally good numbers like 17, 31, 63, + * 127 and 129 have nevertheless a great advantage to the remaining + * numbers in the large set of possible multipliers: their multiply + * operation can be replaced by a faster operation based on just one + * shift plus either a single addition or subtraction operation. And + * because a hash function has to both distribute good _and_ has to + * be very fast to compute, those few numbers should be preferred. + * + * -- Ralf S. Engelschall <rse@engelschall.com> + */ + hash = 0; + if (klen == CACHE_HASH_KEY_STRING) { + for (p = key; *p; p++) { + hash = hash * 33 + *p; + } + klen = p - (const unsigned char *)key; + } + else { + for (p = key, i = klen; i; i--, p++) { + hash = hash * 33 + *p; + } + } + + /* scan linked list */ + for (hep = &ht->array[hash % ht->max], he = *hep; + he; + hep = &he->next, he = *hep) { + if (he->hash == hash && + he->klen == klen && + memcmp(he->key, key, klen) == 0) + break; + } + if (he || !val) + return hep; + /* add a new entry for non-NULL values */ + he = malloc(sizeof(*he)); + if (!he) { + return NULL; + } + he->next = NULL; + he->hash = hash; + he->key = key; + he->klen = klen; + he->val = val; + *hep = he; + ht->count++; + return hep; +} + +CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht, + const void *key, + apr_ssize_t klen) +{ + cache_hash_entry_t *he; + he = *find_entry(ht, key, klen, NULL); + if (he) + return (void *)he->val; + else + return NULL; +} + +CACHE_DECLARE(void *) cache_hash_set(cache_hash_t *ht, + const void *key, + apr_ssize_t klen, + const void *val) +{ + cache_hash_entry_t **hep, *tmp; + const void *tval; + hep = find_entry(ht, key, klen, val); + /* If hep == NULL, then the malloc() in find_entry failed */ + if (hep && *hep) { + if (!val) { + /* delete entry */ + tval = (*hep)->val; + tmp = *hep; + *hep = (*hep)->next; + free(tmp); + --ht->count; + } + else { + /* replace entry */ + tval = (*hep)->val; + (*hep)->val = val; + } + /* Return the object just removed from the cache to let the + * caller clean it up. Cast the constness away upon return. + */ + return (void *) tval; + } + /* else key not present and val==NULL */ + return NULL; +} + +CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht) +{ + return ht->count; +} diff --git a/modules/cache/cache_hash.h b/modules/cache/cache_hash.h new file mode 100644 index 00000000..4138aca7 --- /dev/null +++ b/modules/cache/cache_hash.h @@ -0,0 +1,159 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file cache_hash.h + * @brief Cache Hash Tables + * + * @defgroup Cache_Hash Hash Tables + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_HASH_H +#define CACHE_HASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mod_cache.h" + +/** + * When passing a key to cache_hash_set or cache_hash_get, this value can be + * passed to indicate a string-valued key, and have cache_hash compute the + * length automatically. + * + * @remark cache_hash will use strlen(key) for the length. The null-terminator + * is not included in the hash value (why throw a constant in?). + * Since the hash table merely references the provided key (rather + * than copying it), cache_hash_this() will return the null-term'd key. + */ +#define CACHE_HASH_KEY_STRING (-1) + +/** + * Abstract type for hash tables. + */ +typedef struct cache_hash_t cache_hash_t; + +/** + * Abstract type for scanning hash tables. + */ +typedef struct cache_hash_index_t cache_hash_index_t; + +/** + * Create a hash table. + * @param size + * @return The hash table just created + */ +CACHE_DECLARE(cache_hash_t *) cache_hash_make(apr_size_t size); + +/** + * Create a hash table. + * @param *ht Pointer to the hash table to be freed. + * @return void + * @remark The caller should ensure that all objects have been removed + * from the cache prior to calling cache_hash_free(). Objects + * not removed from the cache prior to calling cache_hash_free() + * will be unaccessable. + */ +CACHE_DECLARE(void) cache_hash_free(cache_hash_t *ht); + + +/** + * Associate a value with a key in a hash table. + * @param ht The hash table + * @param key Pointer to the key + * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length. + * @param val Value to associate with the key + * @remark If the value is NULL the hash entry is deleted. + * @return The value of the deleted cache entry (so the caller can clean it up). + */ +CACHE_DECLARE(void *) cache_hash_set(cache_hash_t *ht, const void *key, + apr_ssize_t klen, const void *val); + +/** + * Look up the value associated with a key in a hash table. + * @param ht The hash table + * @param key Pointer to the key + * @param klen Length of the key. Can be CACHE_HASH_KEY_STRING to use the string length. + * @return Returns NULL if the key is not present. + */ +CACHE_DECLARE(void *) cache_hash_get(cache_hash_t *ht, const void *key, + apr_ssize_t klen); + +/** + * Start iterating over the entries in a hash table. + * @param ht The hash table + * @example + */ +/** + * <PRE> + * + * int sum_values(cache_hash_t *ht) + * { + * cache_hash_index_t *hi; + * void *val; + * int sum = 0; + * for (hi = cache_hash_first(ht); hi; hi = cache_hash_next(hi)) { + * cache_hash_this(hi, NULL, NULL, &val); + * sum += *(int *)val; + * } + * return sum; + * } + * + * There is no restriction on adding or deleting hash entries during an + * iteration (although the results may be unpredictable unless all you do + * is delete the current entry) and multiple iterations can be in + * progress at the same time. + * </PRE> + */ +CACHE_DECLARE(cache_hash_index_t *) cache_hash_first(cache_hash_t *ht); + +/** + * Continue iterating over the entries in a hash table. + * @param hi The iteration state + * @return a pointer to the updated iteration state. NULL if there are no more + * entries. + */ +CACHE_DECLARE(cache_hash_index_t *) cache_hash_next(cache_hash_index_t *hi); + +/** + * Get the current entry's details from the iteration state. + * @param hi The iteration state + * @param key Return pointer for the pointer to the key. + * @param klen Return pointer for the key length. + * @param val Return pointer for the associated value. + * @remark The return pointers should point to a variable that will be set to the + * corresponding data, or they may be NULL if the data isn't interesting. + */ +CACHE_DECLARE(void) cache_hash_this(cache_hash_index_t *hi, const void **key, + apr_ssize_t *klen, void **val); + +/** + * Get the number of key/value pairs in the hash table. + * @param ht The hash table + * @return The number of key/value pairs in the hash table. + */ +CACHE_DECLARE(int) cache_hash_count(cache_hash_t *ht); + + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* !CACHE_HASH_H */ diff --git a/modules/cache/cache_pqueue.c b/modules/cache/cache_pqueue.c new file mode 100644 index 00000000..580b47e7 --- /dev/null +++ b/modules/cache/cache_pqueue.c @@ -0,0 +1,290 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_general.h" + +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if APR_HAVE_STDIO_H +#include <stdio.h> +#endif + +#if APR_HAVE_STRING_H +#include <string.h> +#endif + +#include "cache_pqueue.h" +#define left(i) (2*(i)) +#define right(i) ((2*(i))+1) +#define parent(i) ((i)/2) +/* + * Priority queue structure + */ +struct cache_pqueue_t +{ + apr_ssize_t size; + apr_ssize_t avail; + apr_ssize_t step; + cache_pqueue_get_priority pri; + cache_pqueue_getpos get; + cache_pqueue_setpos set; + void **d; +}; + +cache_pqueue_t *cache_pq_init(apr_ssize_t n, + cache_pqueue_get_priority pri, + cache_pqueue_getpos get, + cache_pqueue_setpos set) +{ + cache_pqueue_t *q; + + if (!(q = malloc(sizeof(cache_pqueue_t)))) { + return NULL; + } + + /* Need to allocate n+1 elements since element 0 isn't used. */ + if (!(q->d = malloc(sizeof(void*) * (n+1)))) { + free(q); + return NULL; + } + q->avail = q->step = (n+1); /* see comment above about n+1 */ + q->pri = pri; + q->size = 1; + q->get = get; + q->set = set; + return q; +} +/* + * cleanup + */ +void cache_pq_free(cache_pqueue_t *q) +{ + free(q->d); + free(q); +} +/* + * pqsize: size of the queue. + */ +apr_ssize_t cache_pq_size(cache_pqueue_t *q) +{ + /* queue element 0 exists but doesn't count since it isn't used. */ + return (q->size - 1); +} + +static void cache_pq_bubble_up(cache_pqueue_t *q, apr_ssize_t i) +{ + apr_ssize_t parent_node; + void *moving_node = q->d[i]; + long moving_pri = q->pri(moving_node); + + for (parent_node = parent(i); + ((i > 1) && (q->pri(q->d[parent_node]) < moving_pri)); + i = parent_node, parent_node = parent(i)) + { + q->d[i] = q->d[parent_node]; + q->set(q->d[i], i); + } + + q->d[i] = moving_node; + q->set(moving_node, i); +} + +static apr_ssize_t maxchild(cache_pqueue_t *q, apr_ssize_t i) +{ + apr_ssize_t child_node = left(i); + + if (child_node >= q->size) + return 0; + + if ((child_node+1 < q->size) && + (q->pri(q->d[child_node+1]) > q->pri(q->d[child_node]))) + { + child_node++; /* use right child instead of left */ + } + + return child_node; +} + +static void cache_pq_percolate_down(cache_pqueue_t *q, apr_ssize_t i) +{ + apr_ssize_t child_node; + void *moving_node = q->d[i]; + long moving_pri = q->pri(moving_node); + + while ((child_node = maxchild(q, i)) && + (moving_pri < q->pri(q->d[child_node]))) + { + q->d[i] = q->d[child_node]; + q->set(q->d[i], i); + i = child_node; + } + + q->d[i] = moving_node; + q->set(moving_node, i); +} + +apr_status_t cache_pq_insert(cache_pqueue_t *q, void *d) +{ + void *tmp; + apr_ssize_t i; + apr_ssize_t newsize; + + if (!q) return APR_EGENERAL; + + /* allocate more memory if necessary */ + if (q->size >= q->avail) { + newsize = q->size + q->step; + if (!(tmp = realloc(q->d, sizeof(void*) * newsize))) { + return APR_EGENERAL; + }; + q->d = tmp; + q->avail = newsize; + } + + /* insert item */ + i = q->size++; + q->d[i] = d; + cache_pq_bubble_up(q, i); + return APR_SUCCESS; +} + +/* + * move a existing entry to a new priority + */ +void cache_pq_change_priority(cache_pqueue_t *q, + long old_priority, + long new_priority, + void *d) +{ + apr_ssize_t posn; + + posn = q->get(d); + if (new_priority > old_priority) + cache_pq_bubble_up(q, posn); + else + cache_pq_percolate_down(q, posn); +} + +apr_status_t cache_pq_remove(cache_pqueue_t *q, void *d) +{ + apr_ssize_t posn = q->get(d); + q->d[posn] = q->d[--q->size]; + if (q->pri(q->d[posn]) > q->pri(d)) + cache_pq_bubble_up(q, posn); + else + cache_pq_percolate_down(q, posn); + + return APR_SUCCESS; +} + +void *cache_pq_pop(cache_pqueue_t *q) +{ + void *head; + + if (!q || q->size == 1) + return NULL; + + head = q->d[1]; + q->d[1] = q->d[--q->size]; + cache_pq_percolate_down(q, 1); + + return head; +} + +void *cache_pq_peek(cache_pqueue_t *q) +{ + void *d; + if (!q || q->size == 1) + return NULL; + d = q->d[1]; + return d; +} + +static void cache_pq_set_null( void*d, apr_ssize_t val) +{ + /* do nothing */ +} + +/* + * this is a debug function.. so it's EASY not fast + */ +void cache_pq_dump(cache_pqueue_t *q, + FILE*out, + cache_pqueue_print_entry print) +{ + int i; + + fprintf(stdout,"posn\tleft\tright\tparent\tmaxchild\t...\n"); + for (i = 1; i < q->size ;i++) { + fprintf(stdout, + "%d\t%d\t%d\t%d\t%" APR_SSIZE_T_FMT "\t", + i, + left(i), right(i), parent(i), + maxchild(q, i)); + print(out, q->d[i]); + } +} + +/* + * this is a debug function.. so it's EASY not fast + */ +void cache_pq_print(cache_pqueue_t *q, + FILE*out, + cache_pqueue_print_entry print) +{ + cache_pqueue_t *dup; + dup = cache_pq_init(q->size, q->pri, q->get, cache_pq_set_null); + dup->size = q->size; + dup->avail = q->avail; + dup->step = q->step; + + memcpy(dup->d, q->d, q->size*sizeof(void*)); + + while (cache_pq_size(dup) > 1) { + void *e = NULL; + e = cache_pq_pop(dup); + if (e) + print(out, e); + else + break; + } + cache_pq_free(dup); +} + +static int cache_pq_subtree_is_valid(cache_pqueue_t *q, int pos) +{ + if (left(pos) < q->size) { + /* has a left child */ + if (q->pri(q->d[pos]) < q->pri(q->d[left(pos)])) + return 0; + if (!cache_pq_subtree_is_valid(q, left(pos))) + return 0; + } + if (right(pos) < q->size) { + /* has a right child */ + if (q->pri(q->d[pos]) < q->pri(q->d[right(pos)])) + return 0; + if (!cache_pq_subtree_is_valid(q, right(pos))) + return 0; + } + return 1; +} + +int cache_pq_is_valid(cache_pqueue_t *q) +{ + return cache_pq_subtree_is_valid(q, 1); +} diff --git a/modules/cache/cache_pqueue.h b/modules/cache/cache_pqueue.h new file mode 100644 index 00000000..13238c03 --- /dev/null +++ b/modules/cache/cache_pqueue.h @@ -0,0 +1,170 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file cache_pqueue.h + * @brief Cache Priority Queue function declarations + * + * @defgroup MOD_CACHE_QUEUE Priority Queue + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_PQUEUE_H +#define CACHE_PQUEUE_H + +#include <apr.h> +#include <apr_errno.h> + +#if APR_HAVE_STDIO_H +#include <stdio.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** the cache priority queue handle */ +typedef struct cache_pqueue_t cache_pqueue_t; + +/** + * callback function to assign a priority for a element + * @param a the element + * @return the score (the lower the score the longer it is kept int the queue) + */ +typedef long (*cache_pqueue_set_priority)(long queue_clock, void *a); +typedef long (*cache_pqueue_get_priority)(void *a); + +/** callback function to get a position of a element */ +typedef apr_ssize_t (*cache_pqueue_getpos)(void *a); + +/** + * callback function to set a position of a element + * @param a the element + * @param pos the position to set it to + */ +typedef void (*cache_pqueue_setpos)(void *a, apr_ssize_t pos); + +/** debug callback function to print a entry */ +typedef void (*cache_pqueue_print_entry)(FILE *out, void *a); + +/** + * initialize the queue + * + * @param n the initial estimate of the number of queue items for which memory + * should be preallocated + * @param pri the callback function to run to assign a score to a element + * @param get the callback function to get the current element's position + * @param set the callback function to set the current element's position + * + * @Return the handle or NULL for insufficent memory + */ +cache_pqueue_t *cache_pq_init(apr_ssize_t n, + cache_pqueue_get_priority pri, + cache_pqueue_getpos get, + cache_pqueue_setpos set); +/** + * free all memory used by the queue + * @param q the queue + */ +void cache_pq_free(cache_pqueue_t *q); +/** + * return the size of the queue. + * @param q the queue + */ +apr_ssize_t cache_pq_size(cache_pqueue_t *q); + +/** + * insert an item into the queue. + * @param q the queue + * @param d the item + * @return APR_SUCCESS on success + */ +apr_status_t cache_pq_insert(cache_pqueue_t *q, void *d); + +/* + * move a existing entry to a different priority + * @param q the queue + * @param old the old priority + * @param d the entry + */ +void cache_pq_change_priority(cache_pqueue_t *q, + long old_priority, + long new_priority, + void *d); + +/** + * pop the highest-ranking item from the queue. + * @param p the queue + * @param d where to copy the entry to + * @return NULL on error, otherwise the entry + */ +void *cache_pq_pop(cache_pqueue_t *q); + +/** + * remove an item from the queue. + * @param p the queue + * @param d the entry + * @return APR_SUCCESS on success + */ +apr_status_t cache_pq_remove(cache_pqueue_t *q, void *d); + +/** + * access highest-ranking item without removing it. + * @param q the queue + * @param d the entry + * @return NULL on error, otherwise the entry + */ +void *cache_pq_peek(cache_pqueue_t *q); + +/** + * print the queue + * @internal + * DEBUG function only + * @param q the queue + * @param out the output handle + * @param the callback function to print the entry + */ +void cache_pq_print(cache_pqueue_t *q, + FILE *out, + cache_pqueue_print_entry print); + +/** + * dump the queue and it's internal structure + * @internal + * debug function only + * @param q the queue + * @param out the output handle + * @param the callback function to print the entry + */ +void cache_pq_dump(cache_pqueue_t *q, + FILE *out, + cache_pqueue_print_entry print); + +/** + * checks that the pq is in the right order, etc + * @internal + * debug function only + * @param q the queue + */ +int cache_pq_is_valid(cache_pqueue_t *q); + +#ifdef __cplusplus +} +#endif + +#endif /* !CACHE_PQUEUE_H */ +/** @} */ diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c new file mode 100644 index 00000000..83352059 --- /dev/null +++ b/modules/cache/cache_storage.c @@ -0,0 +1,433 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CORE_PRIVATE + +#include "mod_cache.h" + +extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key; + +extern module AP_MODULE_DECLARE_DATA cache_module; + +/* -------------------------------------------------------------- */ + +/* + * delete all URL entities from the cache + * + */ +int cache_remove_url(cache_request_rec *cache, apr_pool_t *p) +{ + cache_provider_list *list; + cache_handle_t *h; + + list = cache->providers; + + /* Remove the stale cache entry if present. If not, we're + * being called from outside of a request; remove the + * non-stalle handle. + */ + h = cache->stale_handle ? cache->stale_handle : cache->handle; + if (!h) { + return OK; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "cache: Removing url %s from the cache", h->cache_obj->key); + + /* for each specified cache type, delete the URL */ + while(list) { + list->provider->remove_url(h, p); + list = list->next; + } + return OK; +} + + +/* + * create a new URL entity in the cache + * + * It is possible to store more than once entity per URL. This + * function will always create a new entity, regardless of whether + * other entities already exist for the same URL. + * + * The size of the entity is provided so that a cache module can + * decide whether or not it wants to cache this particular entity. + * If the size is unknown, a size of -1 should be set. + */ +int cache_create_entity(request_rec *r, apr_off_t size) +{ + cache_provider_list *list; + cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t)); + char *key; + apr_status_t rv; + cache_request_rec *cache = (cache_request_rec *) + ap_get_module_config(r->request_config, &cache_module); + + rv = cache_generate_key(r, r->pool, &key); + if (rv != APR_SUCCESS) { + return rv; + } + + list = cache->providers; + /* for each specified cache type, delete the URL */ + while (list) { + switch (rv = list->provider->create_entity(h, r, key, size)) { + case OK: { + cache->handle = h; + cache->provider = list->provider; + cache->provider_name = list->provider_name; + return OK; + } + case DECLINED: { + list = list->next; + continue; + } + default: { + return rv; + } + } + } + return DECLINED; +} + +static int set_cookie_doo_doo(void *v, const char *key, const char *val) +{ + apr_table_addn(v, key, val); + return 1; +} + +CACHE_DECLARE(void) ap_cache_accept_headers(cache_handle_t *h, request_rec *r, + int preserve_orig) +{ + apr_table_t *cookie_table, *hdr_copy; + const char *v; + + v = apr_table_get(h->resp_hdrs, "Content-Type"); + if (v) { + ap_set_content_type(r, v); + apr_table_unset(h->resp_hdrs, "Content-Type"); + /* + * Also unset possible Content-Type headers in r->headers_out and + * r->err_headers_out as they may be different to what we have received + * from the cache. + * Actually they are not needed as r->content_type set by + * ap_set_content_type above will be used in the store_headers functions + * of the storage providers as a fallback and the HTTP_HEADER filter + * does overwrite the Content-Type header with r->content_type anyway. + */ + apr_table_unset(r->headers_out, "Content-Type"); + apr_table_unset(r->err_headers_out, "Content-Type"); + } + + /* If the cache gave us a Last-Modified header, we can't just + * pass it on blindly because of restrictions on future values. + */ + v = apr_table_get(h->resp_hdrs, "Last-Modified"); + if (v) { + ap_update_mtime(r, apr_date_parse_http(v)); + ap_set_last_modified(r); + apr_table_unset(h->resp_hdrs, "Last-Modified"); + } + + /* The HTTP specification says that it is legal to merge duplicate + * headers into one. Some browsers that support Cookies don't like + * merged headers and prefer that each Set-Cookie header is sent + * separately. Lets humour those browsers by not merging. + * Oh what a pain it is. + */ + cookie_table = apr_table_make(r->pool, 2); + apr_table_do(set_cookie_doo_doo, cookie_table, r->err_headers_out, + "Set-Cookie", NULL); + apr_table_do(set_cookie_doo_doo, cookie_table, h->resp_hdrs, + "Set-Cookie", NULL); + apr_table_unset(r->err_headers_out, "Set-Cookie"); + apr_table_unset(h->resp_hdrs, "Set-Cookie"); + + if (preserve_orig) { + hdr_copy = apr_table_copy(r->pool, h->resp_hdrs); + apr_table_overlap(hdr_copy, r->headers_out, APR_OVERLAP_TABLES_SET); + r->headers_out = hdr_copy; + } + else { + apr_table_overlap(r->headers_out, h->resp_hdrs, APR_OVERLAP_TABLES_SET); + } + if (!apr_is_empty_table(cookie_table)) { + r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out, + cookie_table); + } +} + +/* + * select a specific URL entity in the cache + * + * It is possible to store more than one entity per URL. Content + * negotiation is used to select an entity. Once an entity is + * selected, details of it are stored in the per request + * config to save time when serving the request later. + * + * This function returns OK if successful, DECLINED if no + * cached entity fits the bill. + */ +int cache_select(request_rec *r) +{ + cache_provider_list *list; + apr_status_t rv; + cache_handle_t *h; + char *key; + cache_request_rec *cache = (cache_request_rec *) + ap_get_module_config(r->request_config, &cache_module); + + rv = cache_generate_key(r, r->pool, &key); + if (rv != APR_SUCCESS) { + return rv; + } + /* go through the cache types till we get a match */ + h = apr_palloc(r->pool, sizeof(cache_handle_t)); + + list = cache->providers; + + while (list) { + switch ((rv = list->provider->open_entity(h, r, key))) { + case OK: { + char *vary = NULL; + int fresh; + + if (list->provider->recall_headers(h, r) != APR_SUCCESS) { + /* TODO: Handle this error */ + return DECLINED; + } + + /* + * Check Content-Negotiation - Vary + * + * At this point we need to make sure that the object we found in + * the cache is the same object that would be delivered to the + * client, when the effects of content negotiation are taken into + * effect. + * + * In plain english, we want to make sure that a language-negotiated + * document in one language is not given to a client asking for a + * language negotiated document in a different language by mistake. + * + * This code makes the assumption that the storage manager will + * cache the req_hdrs if the response contains a Vary + * header. + * + * RFC2616 13.6 and 14.44 describe the Vary mechanism. + */ + vary = apr_pstrdup(r->pool, apr_table_get(h->resp_hdrs, "Vary")); + while (vary && *vary) { + char *name = vary; + const char *h1, *h2; + + /* isolate header name */ + while (*vary && !apr_isspace(*vary) && (*vary != ',')) + ++vary; + while (*vary && (apr_isspace(*vary) || (*vary == ','))) { + *vary = '\0'; + ++vary; + } + + /* + * is this header in the request and the header in the cached + * request identical? If not, we give up and do a straight get + */ + h1 = apr_table_get(r->headers_in, name); + h2 = apr_table_get(h->req_hdrs, name); + if (h1 == h2) { + /* both headers NULL, so a match - do nothing */ + } + else if (h1 && h2 && !strcmp(h1, h2)) { + /* both headers exist and are equal - do nothing */ + } + else { + /* headers do not match, so Vary failed */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, + "cache_select_url(): Vary header mismatch."); + return DECLINED; + } + } + + cache->provider = list->provider; + cache->provider_name = list->provider_name; + + /* Is our cached response fresh enough? */ + fresh = ap_cache_check_freshness(h, r); + if (!fresh) { + const char *etag, *lastmod; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "Cached response for %s isn't fresh. Adding/replacing " + "conditional request headers.", r->uri); + + /* Make response into a conditional */ + cache->stale_headers = apr_table_copy(r->pool, + r->headers_in); + + /* We can only revalidate with our own conditionals: remove the + * conditions from the original request. + */ + apr_table_unset(r->headers_in, "If-Match"); + apr_table_unset(r->headers_in, "If-Modified-Since"); + apr_table_unset(r->headers_in, "If-None-Match"); + apr_table_unset(r->headers_in, "If-Range"); + apr_table_unset(r->headers_in, "If-Unmodified-Since"); + + etag = apr_table_get(h->resp_hdrs, "ETag"); + lastmod = apr_table_get(h->resp_hdrs, "Last-Modified"); + + if (etag || lastmod) { + /* If we have a cached etag and/or Last-Modified add in + * our own conditionals. + */ + + if (etag) { + apr_table_set(r->headers_in, "If-None-Match", etag); + } + + if (lastmod) { + apr_table_set(r->headers_in, "If-Modified-Since", + lastmod); + } + cache->stale_handle = h; + } + + return DECLINED; + } + + /* Okay, this response looks okay. Merge in our stuff and go. */ + ap_cache_accept_headers(h, r, 0); + + cache->handle = h; + return OK; + } + case DECLINED: { + /* try again with next cache type */ + list = list->next; + continue; + } + default: { + /* oo-er! an error */ + return rv; + } + } + } + return DECLINED; +} + +apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, + char**key) +{ + char *port_str, *hn, *lcs; + const char *hostname, *scheme; + int i; + + /* + * Use the canonical name to improve cache hit rate, but only if this is + * not a proxy request or if this is a reverse proxy request. + * We need to handle both cases in the same manner as for the reverse proxy + * case we have the following situation: + * + * If a cached entry is looked up by mod_cache's quick handler r->proxyreq + * is still unset in the reverse proxy case as it only gets set in the + * translate name hook (either by ProxyPass or mod_rewrite) which is run + * after the quick handler hook. This is different to the forward proxy + * case where it gets set before the quick handler is run (in the + * post_read_request hook). + * If a cache entry is created by the CACHE_SAVE filter we always have + * r->proxyreq set correctly. + * So we must ensure that in the reverse proxy case we use the same code + * path and using the canonical name seems to be the right thing to do + * in the reverse proxy case. + */ + if (!r->proxyreq || (r->proxyreq == PROXYREQ_REVERSE)) { + /* Use _default_ as the hostname if none present, as in mod_vhost */ + hostname = ap_get_server_name(r); + if (!hostname) { + hostname = "_default_"; + } + } + else if(r->parsed_uri.hostname) { + /* Copy the parsed uri hostname */ + hn = apr_pstrdup(p, r->parsed_uri.hostname); + ap_str_tolower(hn); + /* const work-around */ + hostname = hn; + } + else { + /* We are a proxied request, with no hostname. Unlikely + * to get very far - but just in case */ + hostname = "_default_"; + } + + /* + * Copy the scheme, ensuring that it is lower case. If the parsed uri + * contains no string or if this is not a proxy request get the http + * scheme for this request. As r->parsed_uri.scheme is not set if this + * is a reverse proxy request, it is ensured that the cases + * "no proxy request" and "reverse proxy request" are handled in the same + * manner (see above why this is needed). + */ + if (r->proxyreq && r->parsed_uri.scheme) { + /* Copy the scheme and lower-case it */ + lcs = apr_pstrdup(p, r->parsed_uri.scheme); + ap_str_tolower(lcs); + /* const work-around */ + scheme = lcs; + } + else { + scheme = ap_http_scheme(r); + } + + /* + * If this is a proxy request, but not a reverse proxy request (see comment + * above why these cases must be handled in the same manner), copy the + * URI's port-string (which may be a service name). If the URI contains + * no port-string, use apr-util's notion of the default port for that + * scheme - if available. Otherwise use the port-number of the current + * server. + */ + if(r->proxyreq && (r->proxyreq != PROXYREQ_REVERSE)) { + if (r->parsed_uri.port_str) { + port_str = apr_pcalloc(p, strlen(r->parsed_uri.port_str) + 2); + port_str[0] = ':'; + for (i = 0; r->parsed_uri.port_str[i]; i++) { + port_str[i + 1] = apr_tolower(r->parsed_uri.port_str[i]); + } + } + else if (apr_uri_port_of_scheme(scheme)) { + port_str = apr_psprintf(p, ":%u", apr_uri_port_of_scheme(scheme)); + } + else { + /* No port string given in the AbsoluteUri, and we have no + * idea what the default port for the scheme is. Leave it + * blank and live with the inefficiency of some extra cached + * entities. + */ + port_str = ""; + } + } + else { + /* Use the server port */ + port_str = apr_psprintf(p, ":%u", ap_get_server_port(r)); + } + + /* Key format is a URI */ + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, + r->parsed_uri.path, "?", r->args, NULL); + + return APR_SUCCESS; +} diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c new file mode 100644 index 00000000..31cc2c46 --- /dev/null +++ b/modules/cache/cache_util.c @@ -0,0 +1,578 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CORE_PRIVATE + +#include "mod_cache.h" + +#include <ap_provider.h> + +/* -------------------------------------------------------------- */ + +extern module AP_MODULE_DECLARE_DATA cache_module; + +/* Determine if "url" matches the hostname, scheme and port and path + * in "filter". All but the path comparisons are case-insensitive. + */ +static int uri_meets_conditions(apr_uri_t filter, int pathlen, apr_uri_t url) +{ + /* Compare the hostnames */ + if(filter.hostname) { + if (!url.hostname) { + return 0; + } + else if (strcasecmp(filter.hostname, url.hostname)) { + return 0; + } + } + + /* Compare the schemes */ + if(filter.scheme) { + if (!url.scheme) { + return 0; + } + else if (strcasecmp(filter.scheme, url.scheme)) { + return 0; + } + } + + /* Compare the ports */ + if(filter.port_str) { + if (url.port_str && filter.port != url.port) { + return 0; + } + /* NOTE: ap_port_of_scheme will return 0 if given NULL input */ + else if (filter.port != apr_uri_port_of_scheme(url.scheme)) { + return 0; + } + } + else if(url.port_str && filter.scheme) { + if (apr_uri_port_of_scheme(filter.scheme) == url.port) { + return 0; + } + } + + /* Url has met all of the filter conditions so far, determine + * if the paths match. + */ + return !strncmp(filter.path, url.path, pathlen); +} + +CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r, + cache_server_conf *conf, + apr_uri_t uri) +{ + cache_provider_list *providers = NULL; + int i; + + /* loop through all the cacheenable entries */ + for (i = 0; i < conf->cacheenable->nelts; i++) { + struct cache_enable *ent = + (struct cache_enable *)conf->cacheenable->elts; + if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) { + /* Fetch from global config and add to the list. */ + cache_provider *provider; + provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent[i].type, + "0"); + if (!provider) { + /* Log an error! */ + } + else { + cache_provider_list *newp; + newp = apr_pcalloc(r->pool, sizeof(cache_provider_list)); + newp->provider_name = ent[i].type; + newp->provider = provider; + + if (!providers) { + providers = newp; + } + else { + cache_provider_list *last = providers; + + while (last->next) { + last = last->next; + } + last->next = newp; + } + } + } + } + + /* then loop through all the cachedisable entries + * Looking for urls that contain the full cachedisable url and possibly + * more. + * This means we are disabling cachedisable url and below... + */ + for (i = 0; i < conf->cachedisable->nelts; i++) { + struct cache_disable *ent = + (struct cache_disable *)conf->cachedisable->elts; + if (uri_meets_conditions(ent[i].url, ent[i].pathlen, uri)) { + /* Stop searching now. */ + return NULL; + } + } + + return providers; +} + + +/* do a HTTP/1.1 age calculation */ +CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info, + const apr_time_t age_value, + apr_time_t now) +{ + apr_time_t apparent_age, corrected_received_age, response_delay, + corrected_initial_age, resident_time, current_age, + age_value_usec; + + age_value_usec = apr_time_from_sec(age_value); + + /* Perform an HTTP/1.1 age calculation. (RFC2616 13.2.3) */ + + apparent_age = MAX(0, info->response_time - info->date); + corrected_received_age = MAX(apparent_age, age_value_usec); + response_delay = info->response_time - info->request_time; + corrected_initial_age = corrected_received_age + response_delay; + resident_time = now - info->response_time; + current_age = corrected_initial_age + resident_time; + + return apr_time_sec(current_age); +} + +CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, + request_rec *r) +{ + apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale; + apr_int64_t minfresh; + const char *cc_cresp, *cc_req; + const char *pragma; + const char *agestr = NULL; + const char *expstr = NULL; + char *val; + apr_time_t age_c = 0; + cache_info *info = &(h->cache_obj->info); + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * We now want to check if our cached data is still fresh. This depends + * on a few things, in this order: + * + * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache in + * either the request or the cached response means that we must + * revalidate the request unconditionally, overriding any expiration + * mechanism. It's equivalent to max-age=0,must-revalidate. + * + * - RFC2616 14.32 Pragma: no-cache This is treated the same as + * Cache-Control: no-cache. + * + * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate, + * proxy-revalidate if the max-stale request header exists, modify the + * stale calculations below so that an object can be at most <max-stale> + * seconds stale before we request a revalidation, _UNLESS_ a + * must-revalidate or proxy-revalidate cached response header exists to + * stop us doing this. + * + * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the + * maximum age an object can be before it is considered stale. This + * directive has the effect of proxy|must revalidate, which in turn means + * simple ignore any max-stale setting. + * + * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both + * requests and responses. If both are specified, the smaller of the two + * takes priority. + * + * - RFC2616 14.21 Expires: if this request header exists in the cached + * entity, and it's value is in the past, it has expired. + * + */ + + /* This value comes from the client's initial request. */ + cc_req = apr_table_get(r->headers_in, "Cache-Control"); + pragma = apr_table_get(r->headers_in, "Pragma"); + + if (ap_cache_liststr(NULL, pragma, "no-cache", NULL) + || ap_cache_liststr(NULL, cc_req, "no-cache", NULL)) { + + if (!conf->ignorecachecontrol) { + /* Treat as stale, causing revalidation */ + return 0; + } + + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, + "Incoming request is asking for a uncached version of " + "%s, but we know better and are ignoring it", + r->unparsed_uri); + } + + /* These come from the cached entity. */ + cc_cresp = apr_table_get(h->resp_hdrs, "Cache-Control"); + expstr = apr_table_get(h->resp_hdrs, "Expires"); + + if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) { + age_c = apr_atoi64(agestr); + } + + /* calculate age of object */ + age = ap_cache_current_age(info, age_c, r->request_time); + + /* extract s-maxage */ + if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "s-maxage", &val)) { + smaxage = apr_atoi64(val); + } + else { + smaxage = -1; + } + + /* extract max-age from request */ + if (!conf->ignorecachecontrol + && cc_req && ap_cache_liststr(r->pool, cc_req, "max-age", &val)) { + maxage_req = apr_atoi64(val); + } + else { + maxage_req = -1; + } + + /* extract max-age from response */ + if (cc_cresp && ap_cache_liststr(r->pool, cc_cresp, "max-age", &val)) { + maxage_cresp = apr_atoi64(val); + } + else { + maxage_cresp = -1; + } + + /* + * if both maxage request and response, the smaller one takes priority + */ + if (maxage_req == -1) { + maxage = maxage_cresp; + } + else if (maxage_cresp == -1) { + maxage = maxage_req; + } + else { + maxage = MIN(maxage_req, maxage_cresp); + } + + /* extract max-stale */ + if (cc_req && ap_cache_liststr(r->pool, cc_req, "max-stale", &val)) { + maxstale = apr_atoi64(val); + } + else { + maxstale = 0; + } + + /* extract min-fresh */ + if (!conf->ignorecachecontrol + && cc_req && ap_cache_liststr(r->pool, cc_req, "min-fresh", &val)) { + minfresh = apr_atoi64(val); + } + else { + minfresh = 0; + } + + /* override maxstale if must-revalidate or proxy-revalidate */ + if (maxstale && ((cc_cresp && + ap_cache_liststr(NULL, cc_cresp, + "must-revalidate", NULL)) || + (cc_cresp && + ap_cache_liststr(NULL, cc_cresp, + "proxy-revalidate", NULL)))) { + maxstale = 0; + } + + /* handle expiration */ + if (((smaxage != -1) && (age < (smaxage - minfresh))) || + ((maxage != -1) && (age < (maxage + maxstale - minfresh))) || + ((smaxage == -1) && (maxage == -1) && + (info->expire != APR_DATE_BAD) && + (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) { + const char *warn_head; + + warn_head = apr_table_get(h->resp_hdrs, "Warning"); + + /* it's fresh darlings... */ + /* set age header on response */ + apr_table_set(h->resp_hdrs, "Age", + apr_psprintf(r->pool, "%lu", (unsigned long)age)); + + /* add warning if maxstale overrode freshness calculation */ + if (!(((smaxage != -1) && age < smaxage) || + ((maxage != -1) && age < maxage) || + (info->expire != APR_DATE_BAD && + (info->expire - info->date) > age))) { + /* make sure we don't stomp on a previous warning */ + if ((warn_head == NULL) || + ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) { + apr_table_merge(h->resp_hdrs, "Warning", + "110 Response is stale"); + } + } + /* + * If none of Expires, Cache-Control: max-age, or Cache-Control: + * s-maxage appears in the response, and the respose header age + * calculated is more than 24 hours add the warning 113 + */ + if ((maxage_cresp == -1) && (smaxage == -1) && + (expstr == NULL) && (age > 86400)) { + + /* Make sure we don't stomp on a previous warning, and don't dup + * a 113 marning that is already present. Also, make sure to add + * the new warning to the correct *headers_out location. + */ + if ((warn_head == NULL) || + ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) { + apr_table_merge(h->resp_hdrs, "Warning", + "113 Heuristic expiration"); + } + } + return 1; /* Cache object is fresh (enough) */ + } + + return 0; /* Cache object is stale */ +} + +/* + * list is a comma-separated list of case-insensitive tokens, with + * optional whitespace around the tokens. + * The return returns 1 if the token val is found in the list, or 0 + * otherwise. + */ +CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list, + const char *key, char **val) +{ + apr_size_t key_len; + const char *next; + + if (!list) { + return 0; + } + + key_len = strlen(key); + next = list; + + for (;;) { + + /* skip whitespace and commas to find the start of the next key */ + while (*next && (apr_isspace(*next) || (*next == ','))) { + next++; + } + + if (!*next) { + return 0; + } + + if (!strncasecmp(next, key, key_len)) { + /* this field matches the key (though it might just be + * a prefix match, so make sure the match is followed + * by either a space or an equals sign) + */ + next += key_len; + if (!*next || (*next == '=') || apr_isspace(*next) || + (*next == ',')) { + /* valid match */ + if (val) { + while (*next && (*next != '=') && (*next != ',')) { + next++; + } + if (*next == '=') { + next++; + while (*next && apr_isspace(*next )) { + next++; + } + if (!*next) { + *val = NULL; + } + else { + const char *val_start = next; + while (*next && !apr_isspace(*next) && + (*next != ',')) { + next++; + } + *val = apr_pstrmemdup(p, val_start, + next - val_start); + } + } + } + return 1; + } + } + + /* skip to the next field */ + do { + next++; + if (!*next) { + return 0; + } + } while (*next != ','); + } +} + +/* return each comma separated token, one at a time */ +CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list, + const char **str) +{ + apr_size_t i; + const char *s; + + s = ap_strchr_c(list, ','); + if (s != NULL) { + i = s - list; + do + s++; + while (apr_isspace(*s)) + ; /* noop */ + } + else + i = strlen(list); + + while (i > 0 && apr_isspace(list[i - 1])) + i--; + + *str = s; + if (i) + return apr_pstrndup(p, list, i); + else + return NULL; +} + +/* + * Converts apr_time_t expressed as hex digits to + * a true apr_time_t. + */ +CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x) +{ + int i, ch; + apr_time_t j; + for (i = 0, j = 0; i < sizeof(j) * 2; i++) { + ch = x[i]; + j <<= 4; + if (apr_isdigit(ch)) + j |= ch - '0'; + else if (apr_isupper(ch)) + j |= ch - ('A' - 10); + else + j |= ch - ('a' - 10); + } + return j; +} + +/* + * Converts apr_time_t to apr_time_t expressed as hex digits. + */ +CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y) +{ + int i, ch; + + for (i = (sizeof(j) * 2)-1; i >= 0; i--) { + ch = (int)(j & 0xF); + j >>= 4; + if (ch >= 10) + y[i] = ch + ('A' - 10); + else + y[i] = ch + '0'; + } + y[sizeof(j) * 2] = '\0'; +} + +static void cache_hash(const char *it, char *val, int ndepth, int nlength) +{ + apr_md5_ctx_t context; + unsigned char digest[16]; + char tmp[22]; + int i, k, d; + unsigned int x; + static const char enc_table[64] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@"; + + apr_md5_init(&context); + apr_md5_update(&context, (const unsigned char *) it, strlen(it)); + apr_md5_final(digest, &context); + + /* encode 128 bits as 22 characters, using a modified uuencoding + * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is + * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters + */ + for (i = 0, k = 0; i < 15; i += 3) { + x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2]; + tmp[k++] = enc_table[x >> 18]; + tmp[k++] = enc_table[(x >> 12) & 0x3f]; + tmp[k++] = enc_table[(x >> 6) & 0x3f]; + tmp[k++] = enc_table[x & 0x3f]; + } + + /* one byte left */ + x = digest[15]; + tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */ + tmp[k++] = enc_table[(x << 4) & 0x3f]; + + /* now split into directory levels */ + for (i = k = d = 0; d < ndepth; ++d) { + memcpy(&val[i], &tmp[k], nlength); + k += nlength; + val[i + nlength] = '/'; + i += nlength + 1; + } + memcpy(&val[i], &tmp[k], 22 - k); + val[i + 22 - k] = '\0'; +} + +CACHE_DECLARE(char *)ap_cache_generate_name(apr_pool_t *p, int dirlevels, + int dirlength, const char *name) +{ + char hashfile[66]; + cache_hash(name, hashfile, dirlevels, dirlength); + return apr_pstrdup(p, hashfile); +} + +/* Create a new table consisting of those elements from an input + * headers table that are allowed to be stored in a cache. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool, + apr_table_t *t, + server_rec *s) +{ + cache_server_conf *conf; + char **header; + int i; + + /* Make a copy of the headers, and remove from + * the copy any hop-by-hop headers, as defined in Section + * 13.5.1 of RFC 2616 + */ + apr_table_t *headers_out; + headers_out = apr_table_copy(pool, t); + apr_table_unset(headers_out, "Connection"); + apr_table_unset(headers_out, "Keep-Alive"); + apr_table_unset(headers_out, "Proxy-Authenticate"); + apr_table_unset(headers_out, "Proxy-Authorization"); + apr_table_unset(headers_out, "TE"); + apr_table_unset(headers_out, "Trailers"); + apr_table_unset(headers_out, "Transfer-Encoding"); + apr_table_unset(headers_out, "Upgrade"); + + conf = (cache_server_conf *)ap_get_module_config(s->module_config, + &cache_module); + /* Remove the user defined headers set with CacheIgnoreHeaders. + * This may break RFC 2616 compliance on behalf of the administrator. + */ + header = (char **)conf->ignore_headers->elts; + for (i = 0; i < conf->ignore_headers->nelts; i++) { + apr_table_unset(headers_out, header[i]); + } + return headers_out; +} diff --git a/modules/cache/config.m4 b/modules/cache/config.m4 new file mode 100644 index 00000000..9572e06d --- /dev/null +++ b/modules/cache/config.m4 @@ -0,0 +1,26 @@ +dnl modules enabled in this directory by default + +dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]]) + +APACHE_MODPATH_INIT(cache) + +APACHE_MODULE(file_cache, File cache, , , no) + +dnl # list of object files for mod_cache +cache_objs="dnl +mod_cache.lo dnl +cache_storage.lo dnl +cache_util.lo dnl +" +dnl # list of object files for mod_mem_cache +mem_cache_objs="dnl +mod_mem_cache.lo dnl +cache_cache.lo dnl +cache_pqueue.lo dnl +cache_hash.lo dnl +" +APACHE_MODULE(cache, dynamic file caching, $cache_objs, , no) +APACHE_MODULE(disk_cache, disk caching module, , , no) +APACHE_MODULE(mem_cache, memory caching module, $mem_cache_objs, , no) + +APACHE_MODPATH_FINISH diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c new file mode 100644 index 00000000..e492fea5 --- /dev/null +++ b/modules/cache/mod_cache.c @@ -0,0 +1,1249 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define CORE_PRIVATE + +#include "mod_cache.h" + +module AP_MODULE_DECLARE_DATA cache_module; +APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key; + +/* -------------------------------------------------------------- */ + + +/* Handles for cache filters, resolved at startup to eliminate + * a name-to-function mapping on each request + */ +static ap_filter_rec_t *cache_save_filter_handle; +static ap_filter_rec_t *cache_save_subreq_filter_handle; +static ap_filter_rec_t *cache_out_filter_handle; +static ap_filter_rec_t *cache_out_subreq_filter_handle; +static ap_filter_rec_t *cache_remove_url_filter_handle; + +/* + * CACHE handler + * ------------- + * + * Can we deliver this request from the cache? + * If yes: + * deliver the content by installing the CACHE_OUT filter. + * If no: + * check whether we're allowed to try cache it + * If yes: + * add CACHE_SAVE filter + * If No: + * oh well. + */ + +static int cache_url_handler(request_rec *r, int lookup) +{ + apr_status_t rv; + const char *auth; + cache_provider_list *providers; + cache_request_rec *cache; + cache_server_conf *conf; + apr_bucket_brigade *out; + + /* Delay initialization until we know we are handling a GET */ + if (r->method_number != M_GET) { + return DECLINED; + } + + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * Which cache module (if any) should handle this request? + */ + if (!(providers = ap_cache_get_providers(r, conf, r->parsed_uri))) { + return DECLINED; + } + + /* make space for the per request config */ + cache = (cache_request_rec *) ap_get_module_config(r->request_config, + &cache_module); + if (!cache) { + cache = apr_pcalloc(r->pool, sizeof(cache_request_rec)); + ap_set_module_config(r->request_config, &cache_module, cache); + } + + /* save away the possible providers */ + cache->providers = providers; + + /* + * Are we allowed to serve cached info at all? + */ + + /* find certain cache controlling headers */ + auth = apr_table_get(r->headers_in, "Authorization"); + + /* First things first - does the request allow us to return + * cached information at all? If not, just decline the request. + */ + if (auth) { + return DECLINED; + } + + /* + * Try to serve this request from the cache. + * + * If no existing cache file (DECLINED) + * add cache_save filter + * If cached file (OK) + * clear filter stack + * add cache_out filter + * return OK + */ + rv = cache_select(r); + if (rv != OK) { + if (rv == DECLINED) { + if (!lookup) { + + /* + * Add cache_save filter to cache this request. Choose + * the correct filter by checking if we are a subrequest + * or not. + */ + if (r->main) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, + "Adding CACHE_SAVE_SUBREQ filter for %s", + r->uri); + ap_add_output_filter_handle(cache_save_subreq_filter_handle, + NULL, r, r->connection); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, "Adding CACHE_SAVE filter for %s", + r->uri); + ap_add_output_filter_handle(cache_save_filter_handle, + NULL, r, r->connection); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "Adding CACHE_REMOVE_URL filter for %s", + r->uri); + + /* Add cache_remove_url filter to this request to remove a + * stale cache entry if needed. Also put the current cache + * request rec in the filter context, as the request that + * is available later during running the filter maybe + * different due to an internal redirect. + */ + cache->remove_url_filter = + ap_add_output_filter_handle(cache_remove_url_filter_handle, + cache, r, r->connection); + } + else { + if (cache->stale_headers) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, "Restoring request headers for %s", + r->uri); + + r->headers_in = cache->stale_headers; + } + + /* Delete our per-request configuration. */ + ap_set_module_config(r->request_config, &cache_module, NULL); + } + } + else { + /* error */ + ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, + "cache: error returned while checking for cached " + "file by %s cache", cache->provider_name); + } + return DECLINED; + } + + /* if we are a lookup, we are exiting soon one way or another; Restore + * the headers. */ + if (lookup) { + if (cache->stale_headers) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "Restoring request headers."); + r->headers_in = cache->stale_headers; + } + + /* Delete our per-request configuration. */ + ap_set_module_config(r->request_config, &cache_module, NULL); + } + + rv = ap_meets_conditions(r); + if (rv != OK) { + /* If we are a lookup, we have to return DECLINED as we have no + * way of knowing if we will be able to serve the content. + */ + if (lookup) { + return DECLINED; + } + + /* Return cached status. */ + return rv; + } + + /* If we're a lookup, we can exit now instead of serving the content. */ + if (lookup) { + return OK; + } + + /* Serve up the content */ + + /* We are in the quick handler hook, which means that no output + * filters have been set. So lets run the insert_filter hook. + */ + ap_run_insert_filter(r); + + /* + * Add cache_out filter to serve this request. Choose + * the correct filter by checking if we are a subrequest + * or not. + */ + if (r->main) { + ap_add_output_filter_handle(cache_out_subreq_filter_handle, NULL, + r, r->connection); + } + else { + ap_add_output_filter_handle(cache_out_filter_handle, NULL, + r, r->connection); + } + + /* kick off the filter stack */ + out = apr_brigade_create(r->pool, r->connection->bucket_alloc); + rv = ap_pass_brigade(r->output_filters, out); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, + "cache: error returned while trying to return %s " + "cached data", + cache->provider_name); + return rv; + } + + return OK; +} + +/* + * CACHE_OUT filter + * ---------------- + * + * Deliver cached content (headers and body) up the stack. + */ +static int cache_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) +{ + request_rec *r = f->r; + cache_request_rec *cache; + + cache = (cache_request_rec *) ap_get_module_config(r->request_config, + &cache_module); + + if (!cache) { + /* user likely configured CACHE_OUT manually; they should use mod_cache + * configuration to do that */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "CACHE_OUT enabled unexpectedly"); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, bb); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "cache: running CACHE_OUT filter"); + + /* restore status of cached response */ + /* XXX: This exposes a bug in mem_cache, since it does not + * restore the status into it's handle. */ + r->status = cache->handle->cache_obj->info.status; + + /* recall_headers() was called in cache_select() */ + cache->provider->recall_body(cache->handle, r->pool, bb); + + /* This filter is done once it has served up its content */ + ap_remove_output_filter(f); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "cache: serving %s", r->uri); + return ap_pass_brigade(f->next, bb); +} + + +/* + * CACHE_SAVE filter + * --------------- + * + * Decide whether or not this content should be cached. + * If we decide no it should not: + * remove the filter from the chain + * If we decide yes it should: + * Have we already started saving the response? + * If we have started, pass the data to the storage manager via store_body + * Otherwise: + * Check to see if we *can* save this particular response. + * If we can, call cache_create_entity() and save the headers and body + * Finally, pass the data to the next filter (the network or whatever) + */ + +static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) +{ + int rv = !OK; + int date_in_errhdr = 0; + request_rec *r = f->r; + cache_request_rec *cache; + cache_server_conf *conf; + const char *cc_out, *cl; + const char *exps, *lastmods, *dates, *etag; + apr_time_t exp, date, lastmod, now; + apr_off_t size; + cache_info *info = NULL; + char *reason; + apr_pool_t *p; + + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* Setup cache_request_rec */ + cache = (cache_request_rec *) ap_get_module_config(r->request_config, + &cache_module); + if (!cache) { + /* user likely configured CACHE_SAVE manually; they should really use + * mod_cache configuration to do that + */ + cache = apr_pcalloc(r->pool, sizeof(cache_request_rec)); + ap_set_module_config(r->request_config, &cache_module, cache); + } + + reason = NULL; + p = r->pool; + /* + * Pass Data to Cache + * ------------------ + * This section passes the brigades into the cache modules, but only + * if the setup section (see below) is complete. + */ + if (cache->block_response) { + /* We've already sent down the response and EOS. So, ignore + * whatever comes now. + */ + return APR_SUCCESS; + } + + /* have we already run the cachability check and set up the + * cached file handle? + */ + if (cache->in_checked) { + /* pass the brigades into the cache, then pass them + * up the filter stack + */ + rv = cache->provider->store_body(cache->handle, r, in); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "cache: Cache provider's store_body failed!"); + ap_remove_output_filter(f); + } + return ap_pass_brigade(f->next, in); + } + + /* + * Setup Data in Cache + * ------------------- + * This section opens the cache entity and sets various caching + * parameters, and decides whether this URL should be cached at + * all. This section is* run before the above section. + */ + + /* read expiry date; if a bad date, then leave it so the client can + * read it + */ + exps = apr_table_get(r->err_headers_out, "Expires"); + if (exps == NULL) { + exps = apr_table_get(r->headers_out, "Expires"); + } + if (exps != NULL) { + if (APR_DATE_BAD == (exp = apr_date_parse_http(exps))) { + exps = NULL; + } + } + else { + exp = APR_DATE_BAD; + } + + /* read the last-modified date; if the date is bad, then delete it */ + lastmods = apr_table_get(r->err_headers_out, "Last-Modified"); + if (lastmods == NULL) { + lastmods = apr_table_get(r->headers_out, "Last-Modified"); + } + if (lastmods != NULL) { + lastmod = apr_date_parse_http(lastmods); + if (lastmod == APR_DATE_BAD) { + lastmods = NULL; + } + } + else { + lastmod = APR_DATE_BAD; + } + + /* read the etag and cache-control from the entity */ + etag = apr_table_get(r->err_headers_out, "Etag"); + if (etag == NULL) { + etag = apr_table_get(r->headers_out, "Etag"); + } + cc_out = apr_table_get(r->err_headers_out, "Cache-Control"); + if (cc_out == NULL) { + cc_out = apr_table_get(r->headers_out, "Cache-Control"); + } + + /* + * what responses should we not cache? + * + * At this point we decide based on the response headers whether it + * is appropriate _NOT_ to cache the data from the server. There are + * a whole lot of conditions that prevent us from caching this data. + * They are tested here one by one to be clear and unambiguous. + */ + if (r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE + && r->status != HTTP_MULTIPLE_CHOICES + && r->status != HTTP_MOVED_PERMANENTLY + && r->status != HTTP_NOT_MODIFIED) { + /* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410 + * We don't cache 206, because we don't (yet) cache partial responses. + * We include 304 Not Modified here too as this is the origin server + * telling us to serve the cached copy. + */ + reason = apr_psprintf(p, "Response status %d", r->status); + } + else if (exps != NULL && exp == APR_DATE_BAD) { + /* if a broken Expires header is present, don't cache it */ + reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL); + } + else if (r->args && exps == NULL) { + /* if query string present but no expiration time, don't cache it + * (RFC 2616/13.9) + */ + reason = "Query string present but no expires header"; + } + else if (r->status == HTTP_NOT_MODIFIED && + !cache->handle && !cache->stale_handle) { + /* if the server said 304 Not Modified but we have no cache + * file - pass this untouched to the user agent, it's not for us. + */ + reason = "HTTP Status 304 Not Modified"; + } + else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL + && (exps == NULL) && (conf->no_last_mod_ignore ==0)) { + /* 200 OK response from HTTP/1.0 and up without Last-Modified, + * Etag, or Expires headers. + */ + /* Note: mod-include clears last_modified/expires/etags - this + * is why we have an optional function for a key-gen ;-) + */ + reason = "No Last-Modified, Etag, or Expires headers"; + } + else if (r->header_only) { + /* HEAD requests */ + reason = "HTTP HEAD request"; + } + else if (!conf->store_nostore && + ap_cache_liststr(NULL, cc_out, "no-store", NULL)) { + /* RFC2616 14.9.2 Cache-Control: no-store response + * indicating do not cache, or stop now if you are + * trying to cache it. + */ + /* FIXME: The Cache-Control: no-store could have come in on a 304, + * FIXME: while the original request wasn't conditional. IOW, we + * FIXME: made the the request conditional earlier to revalidate + * FIXME: our cached response. + */ + reason = "Cache-Control: no-store present"; + } + else if (!conf->store_private && + ap_cache_liststr(NULL, cc_out, "private", NULL)) { + /* RFC2616 14.9.1 Cache-Control: private response + * this object is marked for this user's eyes only. Behave + * as a tunnel. + */ + /* FIXME: See above (no-store) */ + reason = "Cache-Control: private present"; + } + else if (apr_table_get(r->headers_in, "Authorization") != NULL + && !(ap_cache_liststr(NULL, cc_out, "s-maxage", NULL) + || ap_cache_liststr(NULL, cc_out, "must-revalidate", NULL) + || ap_cache_liststr(NULL, cc_out, "public", NULL))) { + /* RFC2616 14.8 Authorisation: + * if authorisation is included in the request, we don't cache, + * but we can cache if the following exceptions are true: + * 1) If Cache-Control: s-maxage is included + * 2) If Cache-Control: must-revalidate is included + * 3) If Cache-Control: public is included + */ + reason = "Authorization required"; + } + else if (ap_cache_liststr(NULL, + apr_table_get(r->headers_out, "Vary"), + "*", NULL)) { + reason = "Vary header contains '*'"; + } + else if (r->no_cache) { + /* or we've been asked not to cache it above */ + reason = "r->no_cache present"; + } + + if (reason) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache: %s not cached. Reason: %s", r->unparsed_uri, + reason); + + /* remove this filter from the chain */ + ap_remove_output_filter(f); + + /* ship the data up the stack */ + return ap_pass_brigade(f->next, in); + } + + /* Make it so that we don't execute this path again. */ + cache->in_checked = 1; + + /* Set the content length if known. + */ + cl = apr_table_get(r->err_headers_out, "Content-Length"); + if (cl == NULL) { + cl = apr_table_get(r->headers_out, "Content-Length"); + } + if (cl) { + char *errp; + if (apr_strtoff(&size, cl, &errp, 10) || *errp || size < 0) { + cl = NULL; /* parse error, see next 'if' block */ + } + } + + if (!cl) { + /* if we don't get the content-length, see if we have all the + * buckets and use their length to calculate the size + */ + apr_bucket *e; + int all_buckets_here=0; + int unresolved_length = 0; + size=0; + for (e = APR_BRIGADE_FIRST(in); + e != APR_BRIGADE_SENTINEL(in); + e = APR_BUCKET_NEXT(e)) + { + if (APR_BUCKET_IS_EOS(e)) { + all_buckets_here=1; + break; + } + if (APR_BUCKET_IS_FLUSH(e)) { + unresolved_length = 1; + continue; + } + if (e->length == (apr_size_t)-1) { + break; + } + size += e->length; + } + if (!all_buckets_here) { + size = -1; + } + } + + /* It's safe to cache the response. + * + * There are two possiblities at this point: + * - cache->handle == NULL. In this case there is no previously + * cached entity anywhere on the system. We must create a brand + * new entity and store the response in it. + * - cache->stale_handle != NULL. In this case there is a stale + * entity in the system which needs to be replaced by new + * content (unless the result was 304 Not Modified, which means + * the cached entity is actually fresh, and we should update + * the headers). + */ + + /* Did we have a stale cache entry that really is stale? */ + if (cache->stale_handle) { + if (r->status == HTTP_NOT_MODIFIED) { + /* Oh, hey. It isn't that stale! Yay! */ + cache->handle = cache->stale_handle; + info = &cache->handle->cache_obj->info; + rv = OK; + } + else { + /* Oh, well. Toss it. */ + cache->provider->remove_entity(cache->stale_handle); + /* Treat the request as if it wasn't conditional. */ + cache->stale_handle = NULL; + } + } + + /* no cache handle, create a new entity */ + if (!cache->handle) { + rv = cache_create_entity(r, size); + info = apr_pcalloc(r->pool, sizeof(cache_info)); + /* We only set info->status upon the initial creation. */ + info->status = r->status; + } + + if (rv != OK) { + /* Caching layer declined the opportunity to cache the response */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache: Caching url: %s", r->unparsed_uri); + + /* We are actually caching this response. So it does not + * make sense to remove this entity any more. + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache: Removing CACHE_REMOVE_URL filter."); + ap_remove_output_filter(cache->remove_url_filter); + + /* + * We now want to update the cache file header information with + * the new date, last modified, expire and content length and write + * it away to our cache file. First, we determine these values from + * the response, using heuristics if appropriate. + * + * In addition, we make HTTP/1.1 age calculations and write them away + * too. + */ + + /* Read the date. Generate one if one is not supplied */ + dates = apr_table_get(r->err_headers_out, "Date"); + if (dates != NULL) { + date_in_errhdr = 1; + } + else { + dates = apr_table_get(r->headers_out, "Date"); + } + if (dates != NULL) { + info->date = apr_date_parse_http(dates); + } + else { + info->date = APR_DATE_BAD; + } + + now = apr_time_now(); + if (info->date == APR_DATE_BAD) { /* No, or bad date */ + char *dates; + /* no date header (or bad header)! */ + /* add one; N.B. use the time _now_ rather than when we were checking + * the cache + */ + if (date_in_errhdr == 1) { + apr_table_unset(r->err_headers_out, "Date"); + } + date = now; + dates = apr_pcalloc(r->pool, MAX_STRING_LEN); + apr_rfc822_date(dates, now); + apr_table_set(r->headers_out, "Date", dates); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache: Added date header"); + info->date = date; + } + else { + date = info->date; + } + + /* set response_time for HTTP/1.1 age calculations */ + info->response_time = now; + + /* get the request time */ + info->request_time = r->request_time; + + /* check last-modified date */ + if (lastmod != APR_DATE_BAD && lastmod > date) { + /* if it's in the future, then replace by date */ + lastmod = date; + lastmods = dates; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, + r->server, + "cache: Last modified is in the future, " + "replacing with now"); + } + + /* if no expiry date then + * if lastmod + * expiry date = date + min((date - lastmod) * factor, maxexpire) + * else + * expire date = date + defaultexpire + */ + if (exp == APR_DATE_BAD) { + char expire_hdr[APR_RFC822_DATE_LEN]; + + /* if lastmod == date then you get 0*conf->factor which results in + * an expiration time of now. This causes some problems with + * freshness calculations, so we choose the else path... + */ + if ((lastmod != APR_DATE_BAD) && (lastmod < date)) { + apr_time_t x = (apr_time_t) ((date - lastmod) * conf->factor); + + if (x > conf->maxex) { + x = conf->maxex; + } + exp = date + x; + apr_rfc822_date(expire_hdr, exp); + apr_table_set(r->headers_out, "Expires", expire_hdr); + } + else { + exp = date + conf->defex; + apr_rfc822_date(expire_hdr, exp); + apr_table_set(r->headers_out, "Expires", expire_hdr); + } + } + info->expire = exp; + + /* We found a stale entry which wasn't really stale. */ + if (cache->stale_handle) { + /* Load in the saved status and clear the status line. */ + r->status = info->status; + r->status_line = NULL; + + /* RFC 2616 10.3.5 states that entity headers are not supposed + * to be in the 304 response. Therefore, we need to combine the + * response headers with the cached headers *before* we update + * the cached headers. + * + * However, before doing that, we need to first merge in + * err_headers_out and we also need to strip any hop-by-hop + * headers that might have snuck in. + */ + r->headers_out = apr_table_overlay(r->pool, r->headers_out, + r->err_headers_out); + r->headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out, + r->server); + apr_table_clear(r->err_headers_out); + + /* Merge in our cached headers. However, keep any updated values. */ + ap_cache_accept_headers(cache->handle, r, 1); + } + + /* Write away header information to cache. It is possible that we are + * trying to update headers for an entity which has already been cached. + * + * This may fail, due to an unwritable cache area. E.g. filesystem full, + * permissions problems or a read-only (re)mount. This must be handled + * later. + */ + rv = cache->provider->store_headers(cache->handle, r, info); + + /* Did we just update the cached headers on a revalidated response? + * + * If so, we can now decide what to serve to the client. This is done in + * the same way as with a regular response, but conditions are now checked + * against the cached or merged response headers. + */ + if (cache->stale_handle) { + apr_bucket_brigade *bb; + apr_bucket *bkt; + int status; + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + /* Restore the original request headers and see if we need to + * return anything else than the cached response (ie. the original + * request was conditional). + */ + r->headers_in = cache->stale_headers; + status = ap_meets_conditions(r); + if (status != OK) { + r->status = status; + + bkt = apr_bucket_flush_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, bkt); + } + else { + cache->provider->recall_body(cache->handle, r->pool, bb); + } + + cache->block_response = 1; + + /* Before returning we need to handle the possible case of an + * unwritable cache. Rather than leaving the entity in the cache + * and having it constantly re-validated, now that we have recalled + * the body it is safe to try and remove the url from the cache. + */ + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "cache: updating headers with store_headers failed. " + "Removing cached url."); + + rv = cache->provider->remove_url(cache->stale_handle, r->pool); + if (rv != OK) { + /* Probably a mod_disk_cache cache area has been (re)mounted + * read-only, or that there is a permissions problem. + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "cache: attempt to remove url from cache unsuccessful."); + } + } + + return ap_pass_brigade(f->next, bb); + } + + if(rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "cache: store_headers failed"); + ap_remove_output_filter(f); + + return ap_pass_brigade(f->next, in); + } + + rv = cache->provider->store_body(cache->handle, r, in); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "cache: store_body failed"); + ap_remove_output_filter(f); + } + + return ap_pass_brigade(f->next, in); +} + +/* + * CACHE_REMOVE_URL filter + * --------------- + * + * This filter gets added in the quick handler every time the CACHE_SAVE filter + * gets inserted. Its purpose is to remove a confirmed stale cache entry from + * the cache. + * + * CACHE_REMOVE_URL has to be a protocol filter to ensure that is run even if + * the response is a canned error message, which removes the content filters + * and thus the CACHE_SAVE filter from the chain. + * + * CACHE_REMOVE_URL expects cache request rec within its context because the + * request this filter runs on can be different from the one whose cache entry + * should be removed, due to internal redirects. + * + * Note that CACHE_SAVE_URL (as a content-set filter, hence run before the + * protocol filters) will remove this filter if it decides to cache the file. + * Therefore, if this filter is left in, it must mean we need to toss any + * existing files. + */ +static int cache_remove_url_filter(ap_filter_t *f, apr_bucket_brigade *in) +{ + request_rec *r = f->r; + cache_request_rec *cache; + + /* Setup cache_request_rec */ + cache = (cache_request_rec *) f->ctx; + + if (!cache) { + /* user likely configured CACHE_REMOVE_URL manually; they should really + * use mod_cache configuration to do that. So: + * 1. Remove ourselves + * 2. Do nothing and bail out + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache: CACHE_REMOVE_URL enabled unexpectedly"); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); + } + /* Now remove this cache entry from the cache */ + cache_remove_url(cache, r->pool); + + /* remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + +/* -------------------------------------------------------------- */ +/* Setup configurable data */ + +static void * create_cache_config(apr_pool_t *p, server_rec *s) +{ + cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf)); + + /* array of URL prefixes for which caching is enabled */ + ps->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable)); + /* array of URL prefixes for which caching is disabled */ + ps->cachedisable = apr_array_make(p, 10, sizeof(struct cache_disable)); + /* maximum time to cache a document */ + ps->maxex = DEFAULT_CACHE_MAXEXPIRE; + ps->maxex_set = 0; + /* default time to cache a document */ + ps->defex = DEFAULT_CACHE_EXPIRE; + ps->defex_set = 0; + /* factor used to estimate Expires date from LastModified date */ + ps->factor = DEFAULT_CACHE_LMFACTOR; + ps->factor_set = 0; + ps->no_last_mod_ignore_set = 0; + ps->no_last_mod_ignore = 0; + ps->ignorecachecontrol = 0; + ps->ignorecachecontrol_set = 0; + ps->store_private = 0; + ps->store_private_set = 0; + ps->store_nostore = 0; + ps->store_nostore_set = 0; + /* array of headers that should not be stored in cache */ + ps->ignore_headers = apr_array_make(p, 10, sizeof(char *)); + ps->ignore_headers_set = CACHE_IGNORE_HEADERS_UNSET; + return ps; +} + +static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv) +{ + cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf)); + cache_server_conf *base = (cache_server_conf *) basev; + cache_server_conf *overrides = (cache_server_conf *) overridesv; + + /* array of URL prefixes for which caching is disabled */ + ps->cachedisable = apr_array_append(p, + base->cachedisable, + overrides->cachedisable); + /* array of URL prefixes for which caching is enabled */ + ps->cacheenable = apr_array_append(p, + base->cacheenable, + overrides->cacheenable); + /* maximum time to cache a document */ + ps->maxex = (overrides->maxex_set == 0) ? base->maxex : overrides->maxex; + /* default time to cache a document */ + ps->defex = (overrides->defex_set == 0) ? base->defex : overrides->defex; + /* factor used to estimate Expires date from LastModified date */ + ps->factor = + (overrides->factor_set == 0) ? base->factor : overrides->factor; + + ps->no_last_mod_ignore = + (overrides->no_last_mod_ignore_set == 0) + ? base->no_last_mod_ignore + : overrides->no_last_mod_ignore; + ps->ignorecachecontrol = + (overrides->ignorecachecontrol_set == 0) + ? base->ignorecachecontrol + : overrides->ignorecachecontrol; + ps->store_private = + (overrides->store_private_set == 0) + ? base->store_private + : overrides->store_private; + ps->store_nostore = + (overrides->store_nostore_set == 0) + ? base->store_nostore + : overrides->store_nostore; + ps->ignore_headers = + (overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET) + ? base->ignore_headers + : overrides->ignore_headers; + return ps; +} +static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy, + int flag) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->no_last_mod_ignore = flag; + conf->no_last_mod_ignore_set = 1; + return NULL; + +} + +static const char *set_cache_ignore_cachecontrol(cmd_parms *parms, + void *dummy, int flag) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->ignorecachecontrol = flag; + conf->ignorecachecontrol_set = 1; + return NULL; +} + +static const char *set_cache_store_private(cmd_parms *parms, void *dummy, + int flag) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->store_private = flag; + conf->store_private_set = 1; + return NULL; +} + +static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy, + int flag) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->store_nostore = flag; + conf->store_nostore_set = 1; + return NULL; +} + +static const char *add_ignore_header(cmd_parms *parms, void *dummy, + const char *header) +{ + cache_server_conf *conf; + char **new; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + if (!strncasecmp(header, "None", 4)) { + /* if header None is listed clear array */ + conf->ignore_headers->nelts = 0; + } + else { + if ((conf->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET) || + (conf->ignore_headers->nelts)) { + /* Only add header if no "None" has been found in header list + * so far. + * (When 'None' is passed, IGNORE_HEADERS_SET && nelts == 0.) + */ + new = (char **)apr_array_push(conf->ignore_headers); + (*new) = (char *)header; + } + } + conf->ignore_headers_set = CACHE_IGNORE_HEADERS_SET; + return NULL; +} + +static const char *add_cache_enable(cmd_parms *parms, void *dummy, + const char *type, + const char *url) +{ + cache_server_conf *conf; + struct cache_enable *new; + + if (*type == '/') { + return apr_psprintf(parms->pool, + "provider (%s) starts with a '/'. Are url and provider switched?", + type); + } + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + new = apr_array_push(conf->cacheenable); + new->type = type; + if (apr_uri_parse(parms->pool, url, &(new->url))) { + return NULL; + } + if (new->url.path) { + new->pathlen = strlen(new->url.path); + } else { + new->pathlen = 1; + new->url.path = "/"; + } + return NULL; +} + +static const char *add_cache_disable(cmd_parms *parms, void *dummy, + const char *url) +{ + cache_server_conf *conf; + struct cache_disable *new; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + new = apr_array_push(conf->cachedisable); + if (apr_uri_parse(parms->pool, url, &(new->url))) { + return NULL; + } + if (new->url.path) { + new->pathlen = strlen(new->url.path); + } else { + new->pathlen = 1; + new->url.path = "/"; + } + return NULL; +} + +static const char *set_cache_maxex(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC); + conf->maxex_set = 1; + return NULL; +} + +static const char *set_cache_defex(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC); + conf->defex_set = 1; + return NULL; +} + +static const char *set_cache_factor(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + double val; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + if (sscanf(arg, "%lg", &val) != 1) { + return "CacheLastModifiedFactor value must be a float"; + } + conf->factor = val; + conf->factor_set = 1; + return NULL; +} + +static int cache_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + /* This is the means by which unusual (non-unix) os's may find alternate + * means to run a given command (e.g. shebang/registry parsing on Win32) + */ + cache_generate_key = APR_RETRIEVE_OPTIONAL_FN(ap_cache_generate_key); + if (!cache_generate_key) { + cache_generate_key = cache_generate_key_default; + } + return OK; +} + + +static const command_rec cache_cmds[] = +{ + /* XXX + * Consider a new config directive that enables loading specific cache + * implememtations (like mod_cache_mem, mod_cache_file, etc.). + * Rather than using a LoadModule directive, admin would use something + * like CacheModule mem_cache_module | file_cache_module, etc, + * which would cause the approprpriate cache module to be loaded. + * This is more intuitive that requiring a LoadModule directive. + */ + + AP_INIT_TAKE2("CacheEnable", add_cache_enable, NULL, RSRC_CONF, + "A cache type and partial URL prefix below which " + "caching is enabled"), + AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF, + "A partial URL prefix below which caching is disabled"), + AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF, + "The maximum time in seconds to cache a document"), + AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF, + "The default time in seconds to cache a document"), + AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL, + RSRC_CONF, + "Ignore Responses where there is no Last Modified Header"), + AP_INIT_FLAG("CacheIgnoreCacheControl", set_cache_ignore_cachecontrol, + NULL, RSRC_CONF, + "Ignore requests from the client for uncached content"), + AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private, + NULL, RSRC_CONF, + "Ignore 'Cache-Control: private' and store private content"), + AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore, + NULL, RSRC_CONF, + "Ignore 'Cache-Control: no-store' and store sensitive content"), + AP_INIT_ITERATE("CacheIgnoreHeaders", add_ignore_header, NULL, RSRC_CONF, + "A space separated list of headers that should not be " + "stored by the cache"), + AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF, + "The factor used to estimate Expires date from " + "LastModified date"), + {NULL} +}; + +static void register_hooks(apr_pool_t *p) +{ + /* cache initializer */ + /* cache handler */ + ap_hook_quick_handler(cache_url_handler, NULL, NULL, APR_HOOK_FIRST); + /* cache filters + * XXX The cache filters need to run right after the handlers and before + * any other filters. Consider creating AP_FTYPE_CACHE for this purpose. + * + * Depending on the type of request (subrequest / main request) they + * need to be run before AP_FTYPE_CONTENT_SET / after AP_FTYPE_CONTENT_SET + * filters. Thus create two filter handles for each type: + * cache_save_filter_handle / cache_out_filter_handle to be used by + * main requests and + * cache_save_subreq_filter_handle / cache_out_subreq_filter_handle + * to be run by subrequest + */ + /* + * CACHE_SAVE must go into the filter chain after a possible DEFLATE + * filter to ensure that the compressed content is stored. + * Incrementing filter type by 1 ensures his happens. + */ + cache_save_filter_handle = + ap_register_output_filter("CACHE_SAVE", + cache_save_filter, + NULL, + AP_FTYPE_CONTENT_SET+1); + /* + * CACHE_SAVE_SUBREQ must go into the filter chain before SUBREQ_CORE to + * handle subrequsts. Decrementing filter type by 1 ensures this + * happens. + */ + cache_save_subreq_filter_handle = + ap_register_output_filter("CACHE_SAVE_SUBREQ", + cache_save_filter, + NULL, + AP_FTYPE_CONTENT_SET-1); + /* + * CACHE_OUT must go into the filter chain after a possible DEFLATE + * filter to ensure that already compressed cache objects do not + * get compressed again. Incrementing filter type by 1 ensures + * his happens. + */ + cache_out_filter_handle = + ap_register_output_filter("CACHE_OUT", + cache_out_filter, + NULL, + AP_FTYPE_CONTENT_SET+1); + /* + * CACHE_OUT_SUBREQ must go into the filter chain before SUBREQ_CORE to + * handle subrequsts. Decrementing filter type by 1 ensures this + * happens. + */ + cache_out_subreq_filter_handle = + ap_register_output_filter("CACHE_OUT_SUBREQ", + cache_out_filter, + NULL, + AP_FTYPE_CONTENT_SET-1); + /* CACHE_REMOVE_URL has to be a protocol filter to ensure that is + * run even if the response is a canned error message, which + * removes the content filters. + */ + cache_remove_url_filter_handle = + ap_register_output_filter("CACHE_REMOVE_URL", + cache_remove_url_filter, + NULL, + AP_FTYPE_PROTOCOL); + ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); +} + +module AP_MODULE_DECLARE_DATA cache_module = +{ + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + create_cache_config, /* create per-server config structure */ + merge_cache_config, /* merge per-server config structures */ + cache_cmds, /* command apr_table_t */ + register_hooks +}; diff --git a/modules/cache/mod_cache.dsp b/modules/cache/mod_cache.dsp new file mode 100644 index 00000000..f53aca4f --- /dev/null +++ b/modules/cache/mod_cache.dsp @@ -0,0 +1,143 @@ +# Microsoft Developer Studio Project File - Name="mod_cache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_cache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_cache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_cache.mak" CFG="mod_cache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_cache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "MOD_CACHE_EXPORTS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /D "MOD_CACHE_EXPORTS" /Fd"Release\mod_cache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_cache.so" /d "LONG_NAME=cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so /opt:ref + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /Fd"Debug\mod_cache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_cache.so" /d "LONG_NAME=cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so + +!ENDIF + +# Begin Target + +# Name "mod_cache - Win32 Release" +# Name "mod_cache - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\cache_cache.c +# End Source File +# Begin Source File + +SOURCE=.\cache_hash.c +# End Source File +# Begin Source File + +SOURCE=.\cache_pqueue.c +# End Source File +# Begin Source File + +SOURCE=.\cache_storage.c +# End Source File +# Begin Source File + +SOURCE=.\cache_util.c +# End Source File +# Begin Source File + +SOURCE=.\mod_cache.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\cache_cache.h +# End Source File +# Begin Source File + +SOURCE=.\cache_hash.h +# End Source File +# Begin Source File + +SOURCE=.\cache_pqueue.h +# End Source File +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/cache/mod_cache.h b/modules/cache/mod_cache.h new file mode 100644 index 00000000..eb5c8ced --- /dev/null +++ b/modules/cache/mod_cache.h @@ -0,0 +1,327 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file mod_cache.h + * @brief Main include file for the Apache Transparent Cache + * + * @defgroup MOD_CACHE mod_cache + * @ingroup APACHE_MODS + * @{ + */ + +#ifndef MOD_CACHE_H +#define MOD_CACHE_H + +#define CORE_PRIVATE + +#include "apr_hooks.h" +#include "apr.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_md5.h" +#include "apr_pools.h" +#include "apr_strings.h" +#include "apr_optional.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#include "httpd.h" +#include "http_config.h" +#include "ap_config.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_vhost.h" +#include "http_main.h" +#include "http_log.h" +#include "http_connection.h" +#include "util_filter.h" +#include "apr_date.h" +#include "apr_uri.h" + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "apr_atomic.h" + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define MSEC_ONE_DAY ((apr_time_t)(86400*APR_USEC_PER_SEC)) /* one day, in microseconds */ +#define MSEC_ONE_HR ((apr_time_t)(3600*APR_USEC_PER_SEC)) /* one hour, in microseconds */ +#define MSEC_ONE_MIN ((apr_time_t)(60*APR_USEC_PER_SEC)) /* one minute, in microseconds */ +#define MSEC_ONE_SEC ((apr_time_t)(APR_USEC_PER_SEC)) /* one second, in microseconds */ +#define DEFAULT_CACHE_MAXEXPIRE MSEC_ONE_DAY +#define DEFAULT_CACHE_EXPIRE MSEC_ONE_HR +#define DEFAULT_CACHE_LMFACTOR (0.1) + +/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and + * PROXY_DECLARE_DATA with appropriate export and import tags for the platform + */ +#if !defined(WIN32) +#define CACHE_DECLARE(type) type +#define CACHE_DECLARE_NONSTD(type) type +#define CACHE_DECLARE_DATA +#elif defined(CACHE_DECLARE_STATIC) +#define CACHE_DECLARE(type) type __stdcall +#define CACHE_DECLARE_NONSTD(type) type +#define CACHE_DECLARE_DATA +#elif defined(CACHE_DECLARE_EXPORT) +#define CACHE_DECLARE(type) __declspec(dllexport) type __stdcall +#define CACHE_DECLARE_NONSTD(type) __declspec(dllexport) type +#define CACHE_DECLARE_DATA __declspec(dllexport) +#else +#define CACHE_DECLARE(type) __declspec(dllimport) type __stdcall +#define CACHE_DECLARE_NONSTD(type) __declspec(dllimport) type +#define CACHE_DECLARE_DATA __declspec(dllimport) +#endif + +struct cache_enable { + apr_uri_t url; + const char *type; + apr_size_t pathlen; +}; + +struct cache_disable { + apr_uri_t url; + apr_size_t pathlen; +}; + +/* static information about the local cache */ +typedef struct { + apr_array_header_t *cacheenable; /* URLs to cache */ + apr_array_header_t *cachedisable; /* URLs not to cache */ + /* Maximum time to keep cached files in msecs */ + apr_time_t maxex; + int maxex_set; + /* default time to keep cached file in msecs */ + apr_time_t defex; + int defex_set; + /* factor for estimating expires date */ + double factor; + int factor_set; + /** ignore the last-modified header when deciding to cache this request */ + int no_last_mod_ignore_set; + int no_last_mod_ignore; + /** ignore client's requests for uncached responses */ + int ignorecachecontrol; + int ignorecachecontrol_set; + /** ignore Cache-Control: private header from server */ + int store_private; + int store_private_set; + /** ignore Cache-Control: no-store header from client or server */ + int store_nostore; + int store_nostore_set; + /** store the headers that should not be stored in the cache */ + apr_array_header_t *ignore_headers; + /* flag if CacheIgnoreHeader has been set */ + #define CACHE_IGNORE_HEADERS_SET 1 + #define CACHE_IGNORE_HEADERS_UNSET 0 + int ignore_headers_set; +} cache_server_conf; + +/* cache info information */ +typedef struct cache_info cache_info; +struct cache_info { + int status; + apr_time_t date; + apr_time_t expire; + apr_time_t request_time; + apr_time_t response_time; +}; + +/* cache handle information */ + +/* XXX TODO On the next structure change/MMN bump, + * count must become an apr_off_t, representing + * the potential size of disk cached objects. + * Then dig for + * "XXX Bad Temporary Cast - see cache_object_t notes" + */ +typedef struct cache_object cache_object_t; +struct cache_object { + const char *key; + cache_object_t *next; + cache_info info; + /* Opaque portion (specific to the implementation) of the cache object */ + void *vobj; + /* FIXME: These are only required for mod_mem_cache. */ + apr_size_t count; /* Number of body bytes written to the cache so far */ + int complete; + apr_uint32_t refcount; /* refcount and bit flag to cleanup object */ +}; + +typedef struct cache_handle cache_handle_t; +struct cache_handle { + cache_object_t *cache_obj; + apr_table_t *req_hdrs; /* cached request headers */ + apr_table_t *resp_hdrs; /* cached response headers */ +}; + +#define CACHE_PROVIDER_GROUP "cache" + +typedef struct { + int (*remove_entity) (cache_handle_t *h); + apr_status_t (*store_headers)(cache_handle_t *h, request_rec *r, cache_info *i); + apr_status_t (*store_body)(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b); + apr_status_t (*recall_headers) (cache_handle_t *h, request_rec *r); + apr_status_t (*recall_body) (cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); + int (*create_entity) (cache_handle_t *h, request_rec *r, + const char *urlkey, apr_off_t len); + int (*open_entity) (cache_handle_t *h, request_rec *r, + const char *urlkey); + int (*remove_url) (cache_handle_t *h, apr_pool_t *p); +} cache_provider; + +/* A linked-list of authn providers. */ +typedef struct cache_provider_list cache_provider_list; + +struct cache_provider_list { + const char *provider_name; + const cache_provider *provider; + cache_provider_list *next; +}; + +/* per request cache information */ +typedef struct { + cache_provider_list *providers; /* possible cache providers */ + const cache_provider *provider; /* current cache provider */ + const char *provider_name; /* current cache provider name */ + int fresh; /* is the entitey fresh? */ + cache_handle_t *handle; /* current cache handle */ + cache_handle_t *stale_handle; /* stale cache handle */ + apr_table_t *stale_headers; /* original request headers. */ + int in_checked; /* CACHE_SAVE must cache the entity */ + int block_response; /* CACHE_SAVE must block response. */ + apr_bucket_brigade *saved_brigade; /* copy of partial response */ + apr_off_t saved_size; /* length of saved_brigade */ + apr_time_t exp; /* expiration */ + apr_time_t lastmod; /* last-modified time */ + cache_info *info; /* current cache info */ + ap_filter_t *remove_url_filter; /* Enable us to remove the filter */ +} cache_request_rec; + + +/* cache_util.c */ +/* do a HTTP/1.1 age calculation */ +CACHE_DECLARE(apr_time_t) ap_cache_current_age(cache_info *info, const apr_time_t age_value, + apr_time_t now); + +/** + * Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model) + * @param h cache_handle_t + * @param r request_rec + * @return 0 ==> cache object is stale, 1 ==> cache object is fresh + */ +CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, request_rec *r); + +/** + * Merge in cached headers into the response + * @param h cache_handle_t + * @param r request_rec + * @param preserve_orig If 1, the values in r->headers_out are preserved. + * Otherwise, they are overwritten by the cached value. + */ +CACHE_DECLARE(void) ap_cache_accept_headers(cache_handle_t *h, request_rec *r, + int preserve_orig); + +CACHE_DECLARE(apr_time_t) ap_cache_hex2usec(const char *x); +CACHE_DECLARE(void) ap_cache_usec2hex(apr_time_t j, char *y); +CACHE_DECLARE(char *) ap_cache_generate_name(apr_pool_t *p, int dirlevels, + int dirlength, + const char *name); +CACHE_DECLARE(cache_provider_list *)ap_cache_get_providers(request_rec *r, cache_server_conf *conf, apr_uri_t uri); +CACHE_DECLARE(int) ap_cache_liststr(apr_pool_t *p, const char *list, + const char *key, char **val); +CACHE_DECLARE(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list, const char **str); + +/* Create a new table consisting of those elements from a request_rec's + * headers_out that are allowed to be stored in a cache + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_hdrs_out(apr_pool_t *pool, + apr_table_t *t, + server_rec *s); + +/** + * cache_storage.c + */ +int cache_remove_url(cache_request_rec *cache, apr_pool_t *p); +int cache_create_entity(request_rec *r, apr_off_t size); +int cache_select(request_rec *r); +apr_status_t cache_generate_key_default( request_rec *r, apr_pool_t*p, char**key ); +/** + * create a key for the cache based on the request record + * this is the 'default' version, which can be overridden by a default function + */ +const char* cache_create_key( request_rec*r ); + +/* +apr_status_t cache_store_entity_headers(cache_handle_t *h, request_rec *r, cache_info *info); +apr_status_t cache_store_entity_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *bb); + +apr_status_t cache_recall_entity_headers(cache_handle_t *h, request_rec *r); +apr_status_t cache_recall_entity_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); +*/ + +/* hooks */ + +/* Create a set of CACHE_DECLARE(type), CACHE_DECLARE_NONSTD(type) and + * CACHE_DECLARE_DATA with appropriate export and import tags for the platform + */ +#if !defined(WIN32) +#define CACHE_DECLARE(type) type +#define CACHE_DECLARE_NONSTD(type) type +#define CACHE_DECLARE_DATA +#elif defined(CACHE_DECLARE_STATIC) +#define CACHE_DECLARE(type) type __stdcall +#define CACHE_DECLARE_NONSTD(type) type +#define CACHE_DECLARE_DATA +#elif defined(CACHE_DECLARE_EXPORT) +#define CACHE_DECLARE(type) __declspec(dllexport) type __stdcall +#define CACHE_DECLARE_NONSTD(type) __declspec(dllexport) type +#define CACHE_DECLARE_DATA __declspec(dllexport) +#else +#define CACHE_DECLARE(type) __declspec(dllimport) type __stdcall +#define CACHE_DECLARE_NONSTD(type) __declspec(dllimport) type +#define CACHE_DECLARE_DATA __declspec(dllimport) +#endif + +APR_DECLARE_OPTIONAL_FN(apr_status_t, + ap_cache_generate_key, + (request_rec *r, apr_pool_t*p, char**key )); + + +#endif /*MOD_CACHE_H*/ +/** @} */ diff --git a/modules/cache/mod_cache.imp b/modules/cache/mod_cache.imp new file mode 100644 index 00000000..c63910a4 --- /dev/null +++ b/modules/cache/mod_cache.imp @@ -0,0 +1,9 @@ + (MODCACHE) + ap_cache_get_providers, + ap_cache_liststr, + ap_cache_tokstr, + ap_cache_hex2usec, + ap_cache_usec2hex, + ap_cache_cacheable_hdrs_out, + ap_cache_generate_name + diff --git a/modules/cache/mod_disk_cache.c b/modules/cache/mod_disk_cache.c new file mode 100644 index 00000000..15e333c0 --- /dev/null +++ b/modules/cache/mod_disk_cache.c @@ -0,0 +1,1174 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_file_io.h" +#include "apr_strings.h" +#include "mod_cache.h" +#include "mod_disk_cache.h" +#include "ap_provider.h" +#include "util_filter.h" +#include "util_script.h" +#include "util_charset.h" + +/* + * mod_disk_cache: Disk Based HTTP 1.1 Cache. + * + * Flow to Find the .data file: + * Incoming client requests URI /foo/bar/baz + * Generate <hash> off of /foo/bar/baz + * Open <hash>.header + * Read in <hash>.header file (may contain Format #1 or Format #2) + * If format #1 (Contains a list of Vary Headers): + * Use each header name (from .header) with our request values (headers_in) to + * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz + * re-read in <hash>.header (must be format #2) + * read in <hash>.data + * + * Format #1: + * apr_uint32_t format; + * apr_time_t expire; + * apr_array_t vary_headers (delimited by CRLF) + * + * Format #2: + * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format) + * entity name (dobj->name) [length is in disk_cache_info_t->name_len] + * r->headers_out (delimited by CRLF) + * CRLF + * r->headers_in (delimited by CRLF) + * CRLF + */ + +module AP_MODULE_DECLARE_DATA disk_cache_module; + +/* Forward declarations */ +static int remove_entity(cache_handle_t *h); +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i); +static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b); +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r); +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); +static apr_status_t read_array(request_rec *r, apr_array_header_t* arr, + apr_file_t *file); + +/* + * Local static functions + */ + +static char *header_file(apr_pool_t *p, disk_cache_conf *conf, + disk_cache_object_t *dobj, const char *name) +{ + if (!dobj->hashfile) { + dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels, + conf->dirlength, name); + } + + if (dobj->prefix) { + return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/", + dobj->hashfile, CACHE_HEADER_SUFFIX, NULL); + } + else { + return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile, + CACHE_HEADER_SUFFIX, NULL); + } +} + +static char *data_file(apr_pool_t *p, disk_cache_conf *conf, + disk_cache_object_t *dobj, const char *name) +{ + if (!dobj->hashfile) { + dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels, + conf->dirlength, name); + } + + if (dobj->prefix) { + return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/", + dobj->hashfile, CACHE_DATA_SUFFIX, NULL); + } + else { + return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile, + CACHE_DATA_SUFFIX, NULL); + } +} + +static void mkdir_structure(disk_cache_conf *conf, const char *file, apr_pool_t *pool) +{ + apr_status_t rv; + char *p; + + for (p = (char*)file + conf->cache_root_len + 1;;) { + p = strchr(p, '/'); + if (!p) + break; + *p = '\0'; + + rv = apr_dir_make(file, + APR_UREAD|APR_UWRITE|APR_UEXECUTE, pool); + if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) { + /* XXX */ + } + *p = '/'; + ++p; + } +} + +/* htcacheclean may remove directories underneath us. + * So, we'll try renaming three times at a cost of 0.002 seconds. + */ +static apr_status_t safe_file_rename(disk_cache_conf *conf, + const char *src, const char *dest, + apr_pool_t *pool) +{ + apr_status_t rv; + + rv = apr_file_rename(src, dest, pool); + + if (rv != APR_SUCCESS) { + int i; + + for (i = 0; i < 2 && rv != APR_SUCCESS; i++) { + /* 1000 micro-seconds aka 0.001 seconds. */ + apr_sleep(1000); + + mkdir_structure(conf, dest, pool); + + rv = apr_file_rename(src, dest, pool); + } + } + + return rv; +} + +static apr_status_t file_cache_el_final(disk_cache_object_t *dobj, + request_rec *r) +{ + /* move the data over */ + if (dobj->tfd) { + apr_status_t rv; + + apr_file_close(dobj->tfd); + + /* This assumes that the tempfile is on the same file system + * as the cache_root. If not, then we need a file copy/move + * rather than a rename. + */ + rv = apr_file_rename(dobj->tempfile, dobj->datafile, r->pool); + if (rv != APR_SUCCESS) { + /* XXX log */ + } + + dobj->tfd = NULL; + } + + return APR_SUCCESS; +} + +static apr_status_t file_cache_errorcleanup(disk_cache_object_t *dobj, request_rec *r) +{ + /* Remove the header file and the body file. */ + apr_file_remove(dobj->hdrsfile, r->pool); + apr_file_remove(dobj->datafile, r->pool); + + /* If we opened the temporary data file, close and remove it. */ + if (dobj->tfd) { + apr_file_close(dobj->tfd); + apr_file_remove(dobj->tempfile, r->pool); + dobj->tfd = NULL; + } + + return APR_SUCCESS; +} + + +/* These two functions get and put state information into the data + * file for an ap_cache_el, this state information will be read + * and written transparent to clients of this module + */ +static int file_cache_recall_mydata(apr_file_t *fd, cache_info *info, + disk_cache_object_t *dobj, request_rec *r) +{ + apr_status_t rv; + char *urlbuff; + disk_cache_info_t disk_info; + apr_size_t len; + + /* read the data from the cache file */ + len = sizeof(disk_cache_info_t); + rv = apr_file_read_full(fd, &disk_info, len, &len); + if (rv != APR_SUCCESS) { + return rv; + } + + /* Store it away so we can get it later. */ + dobj->disk_info = disk_info; + + info->status = disk_info.status; + info->date = disk_info.date; + info->expire = disk_info.expire; + info->request_time = disk_info.request_time; + info->response_time = disk_info.response_time; + + /* Note that we could optimize this by conditionally doing the palloc + * depending upon the size. */ + urlbuff = apr_palloc(r->pool, disk_info.name_len + 1); + len = disk_info.name_len; + rv = apr_file_read_full(fd, urlbuff, len, &len); + if (rv != APR_SUCCESS) { + return rv; + } + urlbuff[disk_info.name_len] = '\0'; + + /* check that we have the same URL */ + /* Would strncmp be correct? */ + if (strcmp(urlbuff, dobj->name) != 0) { + return APR_EGENERAL; + } + + return APR_SUCCESS; +} + +static const char* regen_key(apr_pool_t *p, apr_table_t *headers, + apr_array_header_t *varray, const char *oldkey) +{ + struct iovec *iov; + int i, k; + int nvec; + const char *header; + const char **elts; + + nvec = (varray->nelts * 2) + 1; + iov = apr_palloc(p, sizeof(struct iovec) * nvec); + elts = (const char **) varray->elts; + + /* TODO: + * - Handle multiple-value headers better. (sort them?) + * - Handle Case in-sensitive Values better. + * This isn't the end of the world, since it just lowers the cache + * hit rate, but it would be nice to fix. + * + * The majority are case insenstive if they are values (encoding etc). + * Most of rfc2616 is case insensitive on header contents. + * + * So the better solution may be to identify headers which should be + * treated case-sensitive? + * HTTP URI's (3.2.3) [host and scheme are insensitive] + * HTTP method (5.1.1) + * HTTP-date values (3.3.1) + * 3.7 Media Types [exerpt] + * The type, subtype, and parameter attribute names are case- + * insensitive. Parameter values might or might not be case-sensitive, + * depending on the semantics of the parameter name. + * 4.20 Except [exerpt] + * Comparison of expectation values is case-insensitive for unquoted + * tokens (including the 100-continue token), and is case-sensitive for + * quoted-string expectation-extensions. + */ + + for(i=0, k=0; i < varray->nelts; i++) { + header = apr_table_get(headers, elts[i]); + if (!header) { + header = ""; + } + iov[k].iov_base = (char*) elts[i]; + iov[k].iov_len = strlen(elts[i]); + k++; + iov[k].iov_base = (char*) header; + iov[k].iov_len = strlen(header); + k++; + } + iov[k].iov_base = (char*) oldkey; + iov[k].iov_len = strlen(oldkey); + k++; + + return apr_pstrcatv(p, iov, k, NULL); +} + +static int array_alphasort(const void *fn1, const void *fn2) +{ + return strcmp(*(char**)fn1, *(char**)fn2); +} + +static void tokens_to_array(apr_pool_t *p, const char *data, + apr_array_header_t *arr) +{ + char *token; + + while ((token = ap_get_list_item(p, &data)) != NULL) { + *((const char **) apr_array_push(arr)) = token; + } + + /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */ + qsort((void *) arr->elts, arr->nelts, + sizeof(char *), array_alphasort); +} + +/* + * Hook and mod_cache callback functions + */ +static int create_entity(cache_handle_t *h, request_rec *r, const char *key, apr_off_t len) +{ + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &disk_cache_module); + cache_object_t *obj; + disk_cache_object_t *dobj; + + if (conf->cache_root == NULL) { + return DECLINED; + } + + /* Allocate and initialize cache_object_t and disk_cache_object_t */ + h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj)); + obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(*dobj)); + + obj->key = apr_pstrdup(r->pool, key); + + dobj->name = obj->key; + dobj->prefix = NULL; + /* Save the cache root */ + dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len); + dobj->root_len = conf->cache_root_len; + dobj->datafile = data_file(r->pool, conf, dobj, key); + dobj->hdrsfile = header_file(r->pool, conf, dobj, key); + dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); + + return OK; +} + +static int open_entity(cache_handle_t *h, request_rec *r, const char *key) +{ + apr_uint32_t format; + apr_size_t len; + const char *nkey; + apr_status_t rc; + static int error_logged = 0; + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &disk_cache_module); + apr_finfo_t finfo; + cache_object_t *obj; + cache_info *info; + disk_cache_object_t *dobj; + int flags; + + h->cache_obj = NULL; + + /* Look up entity keyed to 'url' */ + if (conf->cache_root == NULL) { + if (!error_logged) { + error_logged = 1; + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "disk_cache: Cannot cache files to disk without a CacheRoot specified."); + } + return DECLINED; + } + + /* Create and init the cache object */ + h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(cache_object_t)); + obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(disk_cache_object_t)); + + info = &(obj->info); + + /* Open the headers file */ + dobj->prefix = NULL; + + /* Save the cache root */ + dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len); + dobj->root_len = conf->cache_root_len; + + dobj->hdrsfile = header_file(r->pool, conf, dobj, key); + flags = APR_READ|APR_BINARY|APR_BUFFERED; + rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool); + if (rc != APR_SUCCESS) { + return DECLINED; + } + + /* read the format from the cache file */ + len = sizeof(format); + apr_file_read_full(dobj->hfd, &format, len, &len); + + if (format == VARY_FORMAT_VERSION) { + apr_array_header_t* varray; + apr_time_t expire; + + len = sizeof(expire); + apr_file_read_full(dobj->hfd, &expire, len, &len); + + if (expire < r->request_time) { + return DECLINED; + } + + varray = apr_array_make(r->pool, 5, sizeof(char*)); + rc = read_array(r, varray, dobj->hfd); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, + "disk_cache: Cannot parse vary header file: %s", + dobj->hdrsfile); + return DECLINED; + } + apr_file_close(dobj->hfd); + + nkey = regen_key(r->pool, r->headers_in, varray, key); + + dobj->hashfile = NULL; + dobj->prefix = dobj->hdrsfile; + dobj->hdrsfile = header_file(r->pool, conf, dobj, nkey); + + flags = APR_READ|APR_BINARY|APR_BUFFERED; + rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool); + if (rc != APR_SUCCESS) { + return DECLINED; + } + } + else if (format != DISK_FORMAT_VERSION) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "cache_disk: File '%s' has a version mismatch. File had version: %d.", + dobj->hdrsfile, format); + return DECLINED; + } + else { + apr_off_t offset = 0; + /* This wasn't a Vary Format file, so we must seek to the + * start of the file again, so that later reads work. + */ + apr_file_seek(dobj->hfd, APR_SET, &offset); + nkey = key; + } + + obj->key = nkey; + dobj->key = nkey; + dobj->name = key; + dobj->datafile = data_file(r->pool, conf, dobj, nkey); + dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); + + /* Open the data file */ + flags = APR_READ|APR_BINARY; +#ifdef APR_SENDFILE_ENABLED + flags |= APR_SENDFILE_ENABLED; +#endif + rc = apr_file_open(&dobj->fd, dobj->datafile, flags, 0, r->pool); + if (rc != APR_SUCCESS) { + /* XXX: Log message */ + return DECLINED; + } + + rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, dobj->fd); + if (rc == APR_SUCCESS) { + dobj->file_size = finfo.size; + } + + /* Read the bytes to setup the cache_info fields */ + rc = file_cache_recall_mydata(dobj->hfd, info, dobj, r); + if (rc != APR_SUCCESS) { + /* XXX log message */ + return DECLINED; + } + + /* Initialize the cache_handle callback functions */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "disk_cache: Recalled cached URL info header %s", dobj->name); + return OK; +} + +static int remove_entity(cache_handle_t *h) +{ + /* Null out the cache object pointer so next time we start from scratch */ + h->cache_obj = NULL; + return OK; +} + +static int remove_url(cache_handle_t *h, apr_pool_t *p) +{ + apr_status_t rc; + disk_cache_object_t *dobj; + + /* Get disk cache object from cache handle */ + dobj = (disk_cache_object_t *) h->cache_obj->vobj; + if (!dobj) { + return DECLINED; + } + + /* Delete headers file */ + if (dobj->hdrsfile) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "disk_cache: Deleting %s from cache.", dobj->hdrsfile); + + rc = apr_file_remove(dobj->hdrsfile, p); + if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) { + /* Will only result in an output if httpd is started with -e debug. + * For reason see log_error_core for the case s == NULL. + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL, + "disk_cache: Failed to delete headers file %s from cache.", + dobj->hdrsfile); + return DECLINED; + } + } + + /* Delete data file */ + if (dobj->datafile) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "disk_cache: Deleting %s from cache.", dobj->datafile); + + rc = apr_file_remove(dobj->datafile, p); + if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) { + /* Will only result in an output if httpd is started with -e debug. + * For reason see log_error_core for the case s == NULL. + */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL, + "disk_cache: Failed to delete data file %s from cache.", + dobj->datafile); + return DECLINED; + } + } + + /* now delete directories as far as possible up to our cache root */ + if (dobj->root) { + const char *str_to_copy; + + str_to_copy = dobj->hdrsfile ? dobj->hdrsfile : dobj->datafile; + if (str_to_copy) { + char *dir, *slash, *q; + + dir = apr_pstrdup(p, str_to_copy); + + /* remove filename */ + slash = strrchr(dir, '/'); + *slash = '\0'; + + /* + * now walk our way back to the cache root, delete everything + * in the way as far as possible + * + * Note: due to the way we constructed the file names in + * header_file and data_file, we are guaranteed that the + * cache_root is suffixed by at least one '/' which will be + * turned into a terminating null by this loop. Therefore, + * we won't either delete or go above our cache root. + */ + for (q = dir + dobj->root_len; *q ; ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, + "disk_cache: Deleting directory %s from cache", + dir); + + rc = apr_dir_remove(dir, p); + if (rc != APR_SUCCESS && !APR_STATUS_IS_ENOENT(rc)) { + break; + } + slash = strrchr(q, '/'); + *slash = '\0'; + } + } + } + + return OK; +} + +static apr_status_t read_array(request_rec *r, apr_array_header_t* arr, + apr_file_t *file) +{ + char w[MAX_STRING_LEN]; + int p; + apr_status_t rv; + + while (1) { + rv = apr_file_gets(w, MAX_STRING_LEN - 1, file); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Premature end of vary array."); + return rv; + } + + p = strlen(w); + if (p > 0 && w[p - 1] == '\n') { + if (p > 1 && w[p - 2] == CR) { + w[p - 2] = '\0'; + } + else { + w[p - 1] = '\0'; + } + } + + /* If we've finished reading the array, break out of the loop. */ + if (w[0] == '\0') { + break; + } + + *((const char **) apr_array_push(arr)) = apr_pstrdup(r->pool, w); + } + + return APR_SUCCESS; +} + +static apr_status_t store_array(apr_file_t *fd, apr_array_header_t* arr) +{ + int i; + apr_status_t rv; + struct iovec iov[2]; + apr_size_t amt; + const char **elts; + + elts = (const char **) arr->elts; + + for (i = 0; i < arr->nelts; i++) { + iov[0].iov_base = (char*) elts[i]; + iov[0].iov_len = strlen(elts[i]); + iov[1].iov_base = CRLF; + iov[1].iov_len = sizeof(CRLF) - 1; + + rv = apr_file_writev(fd, (const struct iovec *) &iov, 2, + &amt); + if (rv != APR_SUCCESS) { + return rv; + } + } + + iov[0].iov_base = CRLF; + iov[0].iov_len = sizeof(CRLF) - 1; + + return apr_file_writev(fd, (const struct iovec *) &iov, 1, + &amt); +} + +static apr_status_t read_table(cache_handle_t *handle, request_rec *r, + apr_table_t *table, apr_file_t *file) +{ + char w[MAX_STRING_LEN]; + char *l; + int p; + apr_status_t rv; + + while (1) { + + /* ### What about APR_EOF? */ + rv = apr_file_gets(w, MAX_STRING_LEN - 1, file); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Premature end of cache headers."); + return rv; + } + + /* Delete terminal (CR?)LF */ + + p = strlen(w); + /* Indeed, the host's '\n': + '\012' for UNIX; '\015' for MacOS; '\025' for OS/390 + -- whatever the script generates. + */ + if (p > 0 && w[p - 1] == '\n') { + if (p > 1 && w[p - 2] == CR) { + w[p - 2] = '\0'; + } + else { + w[p - 1] = '\0'; + } + } + + /* If we've finished reading the headers, break out of the loop. */ + if (w[0] == '\0') { + break; + } + +#if APR_CHARSET_EBCDIC + /* Chances are that we received an ASCII header text instead of + * the expected EBCDIC header lines. Try to auto-detect: + */ + if (!(l = strchr(w, ':'))) { + int maybeASCII = 0, maybeEBCDIC = 0; + unsigned char *cp, native; + apr_size_t inbytes_left, outbytes_left; + + for (cp = w; *cp != '\0'; ++cp) { + native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp); + if (apr_isprint(*cp) && !apr_isprint(native)) + ++maybeEBCDIC; + if (!apr_isprint(*cp) && apr_isprint(native)) + ++maybeASCII; + } + if (maybeASCII > maybeEBCDIC) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)", + r->filename); + inbytes_left = outbytes_left = cp - w; + apr_xlate_conv_buffer(ap_hdrs_from_ascii, + w, &inbytes_left, w, &outbytes_left); + } + } +#endif /*APR_CHARSET_EBCDIC*/ + + /* if we see a bogus header don't ignore it. Shout and scream */ + if (!(l = strchr(w, ':'))) { + return APR_EGENERAL; + } + + *l++ = '\0'; + while (*l && apr_isspace(*l)) { + ++l; + } + + apr_table_add(table, w, l); + } + + return APR_SUCCESS; +} + +/* + * Reads headers from a buffer and returns an array of headers. + * Returns NULL on file error + * This routine tries to deal with too long lines and continuation lines. + * @@@: XXX: FIXME: currently the headers are passed thru un-merged. + * Is that okay, or should they be collapsed where possible? + */ +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) +{ + disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj; + + /* This case should not happen... */ + if (!dobj->hfd) { + /* XXX log message */ + return APR_NOTFOUND; + } + + h->req_hdrs = apr_table_make(r->pool, 20); + h->resp_hdrs = apr_table_make(r->pool, 20); + + /* Call routine to read the header lines/status line */ + read_table(h, r, h->resp_hdrs, dobj->hfd); + read_table(h, r, h->req_hdrs, dobj->hfd); + + apr_file_close(dobj->hfd); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "disk_cache: Recalled headers for URL %s", dobj->name); + return APR_SUCCESS; +} + +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb) +{ + apr_bucket *e; + disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj; + + e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, p, + bb->bucket_alloc); + APR_BRIGADE_INSERT_HEAD(bb, e); + e = apr_bucket_eos_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + + return APR_SUCCESS; +} + +static apr_status_t store_table(apr_file_t *fd, apr_table_t *table) +{ + int i; + apr_status_t rv; + struct iovec iov[4]; + apr_size_t amt; + apr_table_entry_t *elts; + + elts = (apr_table_entry_t *) apr_table_elts(table)->elts; + for (i = 0; i < apr_table_elts(table)->nelts; ++i) { + if (elts[i].key != NULL) { + iov[0].iov_base = elts[i].key; + iov[0].iov_len = strlen(elts[i].key); + iov[1].iov_base = ": "; + iov[1].iov_len = sizeof(": ") - 1; + iov[2].iov_base = elts[i].val; + iov[2].iov_len = strlen(elts[i].val); + iov[3].iov_base = CRLF; + iov[3].iov_len = sizeof(CRLF) - 1; + + rv = apr_file_writev(fd, (const struct iovec *) &iov, 4, + &amt); + if (rv != APR_SUCCESS) { + return rv; + } + } + } + iov[0].iov_base = CRLF; + iov[0].iov_len = sizeof(CRLF) - 1; + rv = apr_file_writev(fd, (const struct iovec *) &iov, 1, + &amt); + return rv; +} + +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info) +{ + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &disk_cache_module); + apr_status_t rv; + apr_size_t amt; + disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj; + + disk_cache_info_t disk_info; + struct iovec iov[2]; + + /* This is flaky... we need to manage the cache_info differently */ + h->cache_obj->info = *info; + + if (r->headers_out) { + const char *tmp; + + tmp = apr_table_get(r->headers_out, "Vary"); + + if (tmp) { + apr_array_header_t* varray; + apr_uint32_t format = VARY_FORMAT_VERSION; + + mkdir_structure(conf, dobj->hdrsfile, r->pool); + + rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL, + r->pool); + + if (rv != APR_SUCCESS) { + return rv; + } + + amt = sizeof(format); + apr_file_write(dobj->tfd, &format, &amt); + + amt = sizeof(info->expire); + apr_file_write(dobj->tfd, &info->expire, &amt); + + varray = apr_array_make(r->pool, 6, sizeof(char*)); + tokens_to_array(r->pool, tmp, varray); + + store_array(dobj->tfd, varray); + + apr_file_close(dobj->tfd); + + dobj->tfd = NULL; + + rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, + r->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, + "disk_cache: rename tempfile to varyfile failed: %s -> %s", + dobj->tempfile, dobj->hdrsfile); + return rv; + } + + dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); + tmp = regen_key(r->pool, r->headers_in, varray, dobj->name); + dobj->prefix = dobj->hdrsfile; + dobj->hashfile = NULL; + dobj->datafile = data_file(r->pool, conf, dobj, tmp); + dobj->hdrsfile = header_file(r->pool, conf, dobj, tmp); + } + } + + + rv = apr_file_mktemp(&dobj->hfd, dobj->tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | + APR_BUFFERED | APR_EXCL, r->pool); + + if (rv != APR_SUCCESS) { + return rv; + } + + dobj->name = h->cache_obj->key; + + disk_info.format = DISK_FORMAT_VERSION; + disk_info.date = info->date; + disk_info.expire = info->expire; + disk_info.entity_version = dobj->disk_info.entity_version++; + disk_info.request_time = info->request_time; + disk_info.response_time = info->response_time; + disk_info.status = info->status; + + disk_info.name_len = strlen(dobj->name); + + iov[0].iov_base = (void*)&disk_info; + iov[0].iov_len = sizeof(disk_cache_info_t); + iov[1].iov_base = (void*)dobj->name; + iov[1].iov_len = disk_info.name_len; + + rv = apr_file_writev(dobj->hfd, (const struct iovec *) &iov, 2, &amt); + if (rv != APR_SUCCESS) { + return rv; + } + + if (r->headers_out) { + apr_table_t *headers_out; + + headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out, + r->server); + + if (!apr_table_get(headers_out, "Content-Type") + && r->content_type) { + apr_table_setn(headers_out, "Content-Type", + ap_make_content_type(r, r->content_type)); + } + + headers_out = apr_table_overlay(r->pool, headers_out, + r->err_headers_out); + rv = store_table(dobj->hfd, headers_out); + if (rv != APR_SUCCESS) { + return rv; + } + } + + /* Parse the vary header and dump those fields from the headers_in. */ + /* FIXME: Make call to the same thing cache_select calls to crack Vary. */ + if (r->headers_in) { + apr_table_t *headers_in; + + headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in, + r->server); + rv = store_table(dobj->hfd, headers_in); + if (rv != APR_SUCCESS) { + return rv; + } + } + + apr_file_close(dobj->hfd); /* flush and close */ + + /* Remove old file with the same name. If remove fails, then + * perhaps we need to create the directory tree where we are + * about to write the new headers file. + */ + rv = apr_file_remove(dobj->hdrsfile, r->pool); + if (rv != APR_SUCCESS) { + mkdir_structure(conf, dobj->hdrsfile, r->pool); + } + + rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, + "disk_cache: rename tempfile to hdrsfile failed: %s -> %s", + dobj->tempfile, dobj->hdrsfile); + return rv; + } + + dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "disk_cache: Stored headers for URL %s", dobj->name); + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, + apr_bucket_brigade *bb) +{ + apr_bucket *e; + apr_status_t rv; + disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj; + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &disk_cache_module); + + /* We write to a temp file and then atomically rename the file over + * in file_cache_el_final(). + */ + if (!dobj->tfd) { + rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | + APR_BUFFERED | APR_EXCL, r->pool); + if (rv != APR_SUCCESS) { + return rv; + } + dobj->file_size = 0; + } + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { + const char *str; + apr_size_t length, written; + rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "cache_disk: Error when reading bucket for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return rv; + } + rv = apr_file_write_full(dobj->tfd, str, length, &written); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "cache_disk: Error when writing cache file for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return rv; + } + dobj->file_size += written; + if (dobj->file_size > conf->maxfs) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache_disk: URL %s failed the size check " + "(%" APR_OFF_T_FMT ">%" APR_SIZE_T_FMT ")", + h->cache_obj->key, dobj->file_size, conf->maxfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return APR_EGENERAL; + } + } + + /* Was this the final bucket? If yes, close the temp file and perform + * sanity checks. + */ + if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { + if (r->connection->aborted || r->no_cache) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, + "disk_cache: Discarding body for URL %s " + "because connection has been aborted.", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return APR_EGENERAL; + } + if (dobj->file_size < conf->minfs) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "cache_disk: URL %s failed the size check " + "(%" APR_OFF_T_FMT "<%" APR_SIZE_T_FMT ")", + h->cache_obj->key, dobj->file_size, conf->minfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return APR_EGENERAL; + } + + /* All checks were fine. Move tempfile to final destination */ + /* Link to the perm file, and close the descriptor */ + file_cache_el_final(dobj, r); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "disk_cache: Body for URL %s cached.", dobj->name); + } + + return APR_SUCCESS; +} + +static void *create_config(apr_pool_t *p, server_rec *s) +{ + disk_cache_conf *conf = apr_pcalloc(p, sizeof(disk_cache_conf)); + + /* XXX: Set default values */ + conf->dirlevels = DEFAULT_DIRLEVELS; + conf->dirlength = DEFAULT_DIRLENGTH; + conf->maxfs = DEFAULT_MAX_FILE_SIZE; + conf->minfs = DEFAULT_MIN_FILE_SIZE; + + conf->cache_root = NULL; + conf->cache_root_len = 0; + + return conf; +} + +/* + * mod_disk_cache configuration directives handlers. + */ +static const char +*set_cache_root(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, + &disk_cache_module); + conf->cache_root = arg; + conf->cache_root_len = strlen(arg); + /* TODO: canonicalize cache_root and strip off any trailing slashes */ + + return NULL; +} + +/* + * Consider eliminating the next two directives in favor of + * Ian's prime number hash... + * key = hash_fn( r->uri) + * filename = "/key % prime1 /key %prime2/key %prime3" + */ +static const char +*set_cache_dirlevels(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, + &disk_cache_module); + int val = atoi(arg); + if (val < 1) + return "CacheDirLevels value must be an integer greater than 0"; + if (val * conf->dirlength > CACHEFILE_LEN) + return "CacheDirLevels*CacheDirLength value must not be higher than 20"; + conf->dirlevels = val; + return NULL; +} +static const char +*set_cache_dirlength(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, + &disk_cache_module); + int val = atoi(arg); + if (val < 1) + return "CacheDirLength value must be an integer greater than 0"; + if (val * conf->dirlevels > CACHEFILE_LEN) + return "CacheDirLevels*CacheDirLength value must not be higher than 20"; + + conf->dirlength = val; + return NULL; +} + +static const char +*set_cache_minfs(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, + &disk_cache_module); + conf->minfs = atoi(arg); + return NULL; +} +static const char +*set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, + &disk_cache_module); + conf->maxfs = atoi(arg); + return NULL; +} + +static const command_rec disk_cache_cmds[] = +{ + AP_INIT_TAKE1("CacheRoot", set_cache_root, NULL, RSRC_CONF, + "The directory to store cache files"), + AP_INIT_TAKE1("CacheDirLevels", set_cache_dirlevels, NULL, RSRC_CONF, + "The number of levels of subdirectories in the cache"), + AP_INIT_TAKE1("CacheDirLength", set_cache_dirlength, NULL, RSRC_CONF, + "The number of characters in subdirectory names"), + AP_INIT_TAKE1("CacheMinFileSize", set_cache_minfs, NULL, RSRC_CONF, + "The minimum file size to cache a document"), + AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs, NULL, RSRC_CONF, + "The maximum file size to cache a document"), + {NULL} +}; + +static const cache_provider cache_disk_provider = +{ + &remove_entity, + &store_headers, + &store_body, + &recall_headers, + &recall_body, + &create_entity, + &open_entity, + &remove_url, +}; + +static void disk_cache_register_hook(apr_pool_t *p) +{ + /* cache initializer */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "disk", "0", + &cache_disk_provider); +} + +module AP_MODULE_DECLARE_DATA disk_cache_module = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + create_config, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + disk_cache_cmds, /* command apr_table_t */ + disk_cache_register_hook /* register hooks */ +}; diff --git a/modules/cache/mod_disk_cache.dsp b/modules/cache/mod_disk_cache.dsp new file mode 100644 index 00000000..92a47194 --- /dev/null +++ b/modules/cache/mod_disk_cache.dsp @@ -0,0 +1,103 @@ +# Microsoft Developer Studio Project File - Name="mod_disk_cache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_disk_cache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_disk_cache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_disk_cache.mak" CFG="mod_disk_cache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_disk_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_disk_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_disk_cache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_disk_cache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_disk_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_disk_cache.so" /d "LONG_NAME=disk_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_disk_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_disk_cache.so /opt:ref + +!ELSEIF "$(CFG)" == "mod_disk_cache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_disk_cache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_disk_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_disk_cache.so" /d "LONG_NAME=disk_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_disk_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_disk_cache.so + +!ENDIF + +# Begin Target + +# Name "mod_disk_cache - Win32 Release" +# Name "mod_disk_cache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_disk_cache.c +# End Source File +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/cache/mod_disk_cache.h b/modules/cache/mod_disk_cache.h new file mode 100644 index 00000000..d9911795 --- /dev/null +++ b/modules/cache/mod_disk_cache.h @@ -0,0 +1,95 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MOD_DISK_CACHE_H +#define MOD_DISK_CACHE_H + +/* + * include for mod_disk_cache: Disk Based HTTP 1.1 Cache. + */ + +#define VARY_FORMAT_VERSION 3 +#define DISK_FORMAT_VERSION 4 + +#define CACHE_HEADER_SUFFIX ".header" +#define CACHE_DATA_SUFFIX ".data" +#define CACHE_VDIR_SUFFIX ".vary" + +#define AP_TEMPFILE_PREFIX "/" +#define AP_TEMPFILE_BASE "aptmp" +#define AP_TEMPFILE_SUFFIX "XXXXXX" +#define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE) +#define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX) +#define AP_TEMPFILE AP_TEMPFILE_PREFIX AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX + +typedef struct { + /* Indicates the format of the header struct stored on-disk. */ + apr_uint32_t format; + /* The HTTP status code returned for this response. */ + int status; + /* The size of the entity name that follows. */ + apr_size_t name_len; + /* The number of times we've cached this entity. */ + apr_size_t entity_version; + /* Miscellaneous time values. */ + apr_time_t date; + apr_time_t expire; + apr_time_t request_time; + apr_time_t response_time; +} disk_cache_info_t; + +/* + * disk_cache_object_t + * Pointed to by cache_object_t::vobj + */ +typedef struct disk_cache_object { + const char *root; /* the location of the cache directory */ + apr_size_t root_len; + char *tempfile; /* temp file tohold the content */ + const char *prefix; + const char *datafile; /* name of file where the data will go */ + const char *hdrsfile; /* name of file where the hdrs will go */ + const char *hashfile; /* Computed hash key for this URI */ + const char *name; /* Requested URI without vary bits - suitable for mortals. */ + const char *key; /* On-disk prefix; URI with Vary bits (if present) */ + apr_file_t *fd; /* data file */ + apr_file_t *hfd; /* headers file */ + apr_file_t *tfd; /* temporary file for data */ + apr_off_t file_size; /* File size of the cached data file */ + disk_cache_info_t disk_info; /* Header information. */ +} disk_cache_object_t; + + +/* + * mod_disk_cache configuration + */ +/* TODO: Make defaults OS specific */ +#define CACHEFILE_LEN 20 /* must be less than HASH_LEN/2 */ +#define DEFAULT_DIRLEVELS 3 +#define DEFAULT_DIRLENGTH 2 +#define DEFAULT_MIN_FILE_SIZE 1 +#define DEFAULT_MAX_FILE_SIZE 1000000 + +typedef struct { + const char* cache_root; + apr_size_t cache_root_len; + int dirlevels; /* Number of levels of subdirectories */ + int dirlength; /* Length of subdirectory names */ + apr_size_t minfs; /* minumum file size for cached files */ + apr_size_t maxfs; /* maximum file size for cached files */ +} disk_cache_conf; + +#endif /*MOD_DISK_CACHE_H*/ diff --git a/modules/cache/mod_file_cache.c b/modules/cache/mod_file_cache.c new file mode 100644 index 00000000..17ec6f4a --- /dev/null +++ b/modules/cache/mod_file_cache.c @@ -0,0 +1,417 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Author: mod_file_cache by Bill Stoddard <stoddard apache.org> + * Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org> + * + * v0.01: initial implementation + */ + +/* + Documentation: + + Some sites have a set of static files that are really busy, and + change infrequently (or even on a regular schedule). Save time + by caching open handles to these files. This module, unlike + mod_mmap_static, caches open file handles, not file content. + On systems (like Windows) with heavy system call overhead and + that have an efficient sendfile implementation, caching file handles + offers several advantages over caching content. First, the file system + can manage the memory, allowing infrequently hit cached files to + be paged out. Second, since caching open handles does not consume + significant resources, it will be possible to enable an AutoLoadCache + feature where static files are dynamically loaded in the cache + as the server runs. On systems that have file change notification, + this module can be enhanced to automatically garbage collect + cached files that change on disk. + + This module should work on Unix systems that have sendfile. Place + cachefile directives into your configuration to direct files to + be cached. + + cachefile /path/to/file1 + cachefile /path/to/file2 + ... + + These files are only cached when the server is restarted, so if you + change the list, or if the files are changed, then you'll need to + restart the server. + + To reiterate that point: if the files are modified *in place* + without restarting the server you may end up serving requests that + are completely bogus. You should update files by unlinking the old + copy and putting a new copy in place. + + There's no such thing as inheriting these files across vhosts or + whatever... place the directives in the main server only. + + Known problems: + + Don't use Alias or RewriteRule to move these files around... unless + you feel like paying for an extra stat() on each request. This is + a deficiency in the Apache API that will hopefully be solved some day. + The file will be served out of the file handle cache, but there will be + an extra stat() that's a waste. +*/ + +#include "apr.h" + +#if !(APR_HAS_SENDFILE || APR_HAS_MMAP) +#error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP +#endif + +#include "apr_mmap.h" +#include "apr_strings.h" +#include "apr_hash.h" +#include "apr_buckets.h" + +#define APR_WANT_STRFUNC +#include "apr_want.h" + +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#define CORE_PRIVATE + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_core.h" + +module AP_MODULE_DECLARE_DATA file_cache_module; + +typedef struct { +#if APR_HAS_SENDFILE + apr_file_t *file; +#endif + const char *filename; + apr_finfo_t finfo; + int is_mmapped; +#if APR_HAS_MMAP + apr_mmap_t *mm; +#endif + char mtimestr[APR_RFC822_DATE_LEN]; + char sizestr[21]; /* big enough to hold any 64-bit file size + null */ +} a_file; + +typedef struct { + apr_hash_t *fileht; +} a_server_config; + + +static void *create_server_config(apr_pool_t *p, server_rec *s) +{ + a_server_config *sconf = apr_palloc(p, sizeof(*sconf)); + + sconf->fileht = apr_hash_make(p); + return sconf; +} + +static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap) +{ + a_server_config *sconf; + a_file *new_file; + a_file tmp; + apr_file_t *fd = NULL; + apr_status_t rc; + const char *fspec; + + fspec = ap_server_root_relative(cmd->pool, filename); + if (!fspec) { + ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, + "mod_file_cache: invalid file path " + "%s, skipping", filename); + return; + } + if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN, + cmd->temp_pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, + "mod_file_cache: unable to stat(%s), skipping", fspec); + return; + } + if (tmp.finfo.filetype != APR_REG) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "mod_file_cache: %s isn't a regular file, skipping", fspec); + return; + } + if (tmp.finfo.size > AP_MAX_SENDFILE) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "mod_file_cache: %s is too large to cache, skipping", fspec); + return; + } + + rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD, + APR_OS_DEFAULT, cmd->pool); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, + "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec); + return; + } + apr_file_inherit_set(fd); + + /* WooHoo, we have a file to put in the cache */ + new_file = apr_pcalloc(cmd->pool, sizeof(a_file)); + new_file->finfo = tmp.finfo; + +#if APR_HAS_MMAP + if (mmap) { + /* MMAPFile directive. MMAP'ing the file + * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if + * size is greater than MAX(apr_size_t) (perhaps greater than 1M?). + */ + if ((rc = apr_mmap_create(&new_file->mm, fd, 0, + (apr_size_t)new_file->finfo.size, + APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) { + apr_file_close(fd); + ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, + "mod_file_cache: unable to mmap %s, skipping", filename); + return; + } + apr_file_close(fd); + new_file->is_mmapped = TRUE; + } +#endif +#if APR_HAS_SENDFILE + if (!mmap) { + /* CacheFile directive. Caching the file handle */ + new_file->is_mmapped = FALSE; + new_file->file = fd; + } +#endif + + new_file->filename = fspec; + apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime); + apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size); + + sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module); + apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file); + +} + +static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename) +{ +#if APR_HAS_SENDFILE + cache_the_file(cmd, filename, 0); +#else + /* Sendfile not supported by this OS */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename); +#endif + return NULL; +} +static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename) +{ +#if APR_HAS_MMAP + cache_the_file(cmd, filename, 1); +#else + /* MMAP not supported by this OS */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename); +#endif + return NULL; +} + +static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + /* Hummm, anything to do here? */ + return OK; +} + +/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a + * bit of a kludge, because we really want to run after core_translate runs. + */ +static int file_cache_xlat(request_rec *r) +{ + a_server_config *sconf; + a_file *match; + int res; + + sconf = ap_get_module_config(r->server->module_config, &file_cache_module); + + /* we only operate when at least one cachefile directive was used */ + if (!apr_hash_count(sconf->fileht)) { + return DECLINED; + } + + res = ap_core_translate(r); + if (res != OK || !r->filename) { + return res; + } + + /* search the cache */ + match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING); + if (match == NULL) + return DECLINED; + + /* pass search results to handler */ + ap_set_module_config(r->request_config, &file_cache_module, match); + + /* shortcircuit the get_path_info() stat() calls and stuff */ + r->finfo = match->finfo; + return OK; +} + +static int mmap_handler(request_rec *r, a_file *file) +{ +#if APR_HAS_MMAP + conn_rec *c = r->connection; + apr_bucket *b; + apr_mmap_t *mm; + apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc); + + apr_mmap_dup(&mm, file->mm, r->pool); + b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size, + c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS) + return HTTP_INTERNAL_SERVER_ERROR; +#endif + return OK; +} + +static int sendfile_handler(request_rec *r, a_file *file) +{ +#if APR_HAS_SENDFILE + conn_rec *c = r->connection; + apr_bucket *b; + apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc); + + b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size, + r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS) + return HTTP_INTERNAL_SERVER_ERROR; +#endif + return OK; +} + +static int file_cache_handler(request_rec *r) +{ + a_file *match; + int errstatus; + int rc = OK; + + /* XXX: not sure if this is right yet + * see comment in http_core.c:default_handler + */ + if (ap_strcmp_match(r->handler, "*/*")) { + return DECLINED; + } + + /* we don't handle anything but GET */ + if (r->method_number != M_GET) return DECLINED; + + /* did xlat phase find the file? */ + match = ap_get_module_config(r->request_config, &file_cache_module); + + if (match == NULL) { + return DECLINED; + } + + /* note that we would handle GET on this resource */ + r->allowed |= (AP_METHOD_BIT << M_GET); + + /* This handler has no use for a request body (yet), but we still + * need to read and discard it if the client sent one. + */ + if ((errstatus = ap_discard_request_body(r)) != OK) + return errstatus; + + ap_update_mtime(r, match->finfo.mtime); + + /* ap_set_last_modified() always converts the file mtime to a string + * which is slow. Accelerate the common case. + * ap_set_last_modified(r); + */ + { + apr_time_t mod_time; + char *datestr; + + mod_time = ap_rationalize_mtime(r, r->mtime); + if (mod_time == match->finfo.mtime) + datestr = match->mtimestr; + else { + datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN); + apr_rfc822_date(datestr, mod_time); + } + apr_table_setn(r->headers_out, "Last-Modified", datestr); + } + + ap_set_etag(r); + if ((errstatus = ap_meets_conditions(r)) != OK) { + return errstatus; + } + + /* ap_set_content_length() always converts the same number and never + * returns an error. Accelerate it. + */ + r->clength = match->finfo.size; + apr_table_setn(r->headers_out, "Content-Length", match->sizestr); + + /* Call appropriate handler */ + if (!r->header_only) { + if (match->is_mmapped == TRUE) + rc = mmap_handler(r, match); + else + rc = sendfile_handler(r, match); + } + + return rc; +} + +static command_rec file_cache_cmds[] = +{ +AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF, + "A space separated list of files to add to the file handle cache at config time"), +AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF, + "A space separated list of files to mmap at config time"), + {NULL} +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST); + ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE); + /* This trick doesn't work apparently because the translate hooks + are single shot. If the core_hook returns OK, then our hook is + not called. + ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE); + */ + +} + +module AP_MODULE_DECLARE_DATA file_cache_module = +{ + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + create_server_config, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + file_cache_cmds, /* command handlers */ + register_hooks /* register hooks */ +}; diff --git a/modules/cache/mod_file_cache.dsp b/modules/cache/mod_file_cache.dsp new file mode 100644 index 00000000..3af6e968 --- /dev/null +++ b/modules/cache/mod_file_cache.dsp @@ -0,0 +1,99 @@ +# Microsoft Developer Studio Project File - Name="mod_file_cache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_file_cache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_file_cache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_file_cache.mak" CFG="mod_file_cache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_file_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_file_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_file_cache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_file_cache.so" /d "LONG_NAME=file_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /subsystem:windows /dll /out:"Release/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so +# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so /opt:ref + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_file_cache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_file_cache.so" /d "LONG_NAME=file_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so +# ADD LINK32 /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_file_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so + +!ENDIF + +# Begin Target + +# Name "mod_file_cache - Win32 Release" +# Name "mod_file_cache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_file_cache.c +# End Source File +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project diff --git a/modules/cache/mod_file_cache.exp b/modules/cache/mod_file_cache.exp new file mode 100644 index 00000000..23b092a6 --- /dev/null +++ b/modules/cache/mod_file_cache.exp @@ -0,0 +1 @@ +file_cache_module diff --git a/modules/cache/mod_mem_cache.c b/modules/cache/mod_mem_cache.c new file mode 100644 index 00000000..b9c6915d --- /dev/null +++ b/modules/cache/mod_mem_cache.c @@ -0,0 +1,1095 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rules for managing obj->refcount: + * refcount should be incremented when an object is placed in the cache. Insertion + * of an object into the cache and the refcount increment should happen under + * protection of the sconf->lock. + * + * refcount should be decremented when the object is removed from the cache. + * Object should be removed from the cache and the refcount decremented while + * under protection of the sconf->lock. + * + * refcount should be incremented when an object is retrieved from the cache + * by a worker thread. The retrieval/find operation and refcount increment + * should occur under protection of the sconf->lock + * + * refcount can be atomically decremented w/o protection of the sconf->lock + * by worker threads. + * + * Any object whose refcount drops to 0 should be freed/cleaned up. A refcount + * of 0 means the object is not in the cache and no worker threads are accessing + * it. + */ +#define CORE_PRIVATE +#include "mod_cache.h" +#include "cache_pqueue.h" +#include "cache_cache.h" +#include "ap_provider.h" +#include "ap_mpm.h" +#include "apr_thread_mutex.h" +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if !APR_HAS_THREADS +#error This module does not currently compile unless you have a thread-capable APR. Sorry! +#endif + +module AP_MODULE_DECLARE_DATA mem_cache_module; + +typedef enum { + CACHE_TYPE_FILE = 1, + CACHE_TYPE_HEAP, + CACHE_TYPE_MMAP +} cache_type_e; + +typedef struct { + char* hdr; + char* val; +} cache_header_tbl_t; + +typedef struct mem_cache_object { + cache_type_e type; + apr_ssize_t num_header_out; + apr_ssize_t num_req_hdrs; + cache_header_tbl_t *header_out; + cache_header_tbl_t *req_hdrs; /* for Vary negotiation */ + apr_size_t m_len; + void *m; + apr_os_file_t fd; + apr_int32_t flags; /* File open flags */ + long priority; /**< the priority of this entry */ + long total_refs; /**< total number of references this entry has had */ + + apr_uint32_t pos; /**< the position of this entry in the cache */ + +} mem_cache_object_t; + +typedef struct { + apr_thread_mutex_t *lock; + cache_cache_t *cache_cache; + + /* Fields set by config directives */ + apr_size_t min_cache_object_size; /* in bytes */ + apr_size_t max_cache_object_size; /* in bytes */ + apr_size_t max_cache_size; /* in bytes */ + apr_size_t max_object_cnt; + cache_pqueue_set_priority cache_remove_algorithm; + + /* maximum amount of data to buffer on a streamed response where + * we haven't yet seen EOS */ + apr_off_t max_streaming_buffer_size; +} mem_cache_conf; +static mem_cache_conf *sconf; + +#define DEFAULT_MAX_CACHE_SIZE 100*1024 +#define DEFAULT_MIN_CACHE_OBJECT_SIZE 0 +#define DEFAULT_MAX_CACHE_OBJECT_SIZE 10000 +#define DEFAULT_MAX_OBJECT_CNT 1009 +#define DEFAULT_MAX_STREAMING_BUFFER_SIZE 100000 +#define CACHEFILE_LEN 20 + +/* Forward declarations */ +static int remove_entity(cache_handle_t *h); +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *i); +static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b); +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r); +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); + +static void cleanup_cache_object(cache_object_t *obj); + +static long memcache_get_priority(void*a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + + return mobj->priority; +} + +static void memcache_inc_frequency(void*a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + + mobj->total_refs++; + mobj->priority = 0; +} + +static void memcache_set_pos(void *a, apr_ssize_t pos) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + + apr_atomic_set32(&mobj->pos, pos); +} +static apr_ssize_t memcache_get_pos(void *a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + + return apr_atomic_read32(&mobj->pos); +} + +static apr_size_t memcache_cache_get_size(void*a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + return mobj->m_len; +} +/** callback to get the key of a item */ +static const char* memcache_cache_get_key(void*a) +{ + cache_object_t *obj = (cache_object_t *)a; + return obj->key; +} +/** + * memcache_cache_free() + * memcache_cache_free is a callback that is only invoked by a thread + * running in cache_insert(). cache_insert() runs under protection + * of sconf->lock. By the time this function has been entered, the cache_object + * has been ejected from the cache. decrement the refcount and if the refcount drops + * to 0, cleanup the cache object. + */ +static void memcache_cache_free(void*a) +{ + cache_object_t *obj = (cache_object_t *)a; + + /* Decrement the refcount to account for the object being ejected + * from the cache. If the refcount is 0, free the object. + */ + if (!apr_atomic_dec32(&obj->refcount)) { + cleanup_cache_object(obj); + } +} +/* + * functions return a 'negative' score since priority queues + * dequeue the object with the highest value first + */ +static long memcache_lru_algorithm(long queue_clock, void *a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + if (mobj->priority == 0) + mobj->priority = queue_clock - mobj->total_refs; + + /* + * a 'proper' LRU function would just be + * mobj->priority = mobj->total_refs; + */ + return mobj->priority; +} + +static long memcache_gdsf_algorithm(long queue_clock, void *a) +{ + cache_object_t *obj = (cache_object_t *)a; + mem_cache_object_t *mobj = obj->vobj; + + if (mobj->priority == 0) + mobj->priority = queue_clock - + (long)(mobj->total_refs*1000 / mobj->m_len); + + return mobj->priority; +} + +static void cleanup_cache_object(cache_object_t *obj) +{ + mem_cache_object_t *mobj = obj->vobj; + + /* TODO: + * We desperately need a more efficient way of allocating objects. We're + * making way too many malloc calls to create a fully populated + * cache object... + */ + + /* Cleanup the cache_object_t */ + if (obj->key) { + free((void*)obj->key); + } + + free(obj); + + /* Cleanup the mem_cache_object_t */ + if (mobj) { + if (mobj->type == CACHE_TYPE_HEAP && mobj->m) { + free(mobj->m); + } + if (mobj->type == CACHE_TYPE_FILE && mobj->fd) { +#ifdef WIN32 + CloseHandle(mobj->fd); +#else + close(mobj->fd); +#endif + } + if (mobj->header_out) { + if (mobj->header_out[0].hdr) + free(mobj->header_out[0].hdr); + free(mobj->header_out); + } + if (mobj->req_hdrs) { + if (mobj->req_hdrs[0].hdr) + free(mobj->req_hdrs[0].hdr); + free(mobj->req_hdrs); + } + free(mobj); + } +} +static apr_status_t decrement_refcount(void *arg) +{ + cache_object_t *obj = (cache_object_t *) arg; + + /* If obj->complete is not set, the cache update failed and the + * object needs to be removed from the cache then cleaned up. + * The garbage collector may have ejected the object from the + * cache already, so make sure it is really still in the cache + * before attempting to remove it. + */ + if (!obj->complete) { + cache_object_t *tobj = NULL; + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + tobj = cache_find(sconf->cache_cache, obj->key); + if (tobj == obj) { + cache_remove(sconf->cache_cache, obj); + apr_atomic_dec32(&obj->refcount); + } + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + } + + /* If the refcount drops to 0, cleanup the cache object */ + if (!apr_atomic_dec32(&obj->refcount)) { + cleanup_cache_object(obj); + } + return APR_SUCCESS; +} +static apr_status_t cleanup_cache_mem(void *sconfv) +{ + cache_object_t *obj; + mem_cache_conf *co = (mem_cache_conf*) sconfv; + + if (!co) { + return APR_SUCCESS; + } + if (!co->cache_cache) { + return APR_SUCCESS; + } + + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + obj = cache_pop(co->cache_cache); + while (obj) { + /* Iterate over the cache and clean up each unreferenced entry */ + if (!apr_atomic_dec32(&obj->refcount)) { + cleanup_cache_object(obj); + } + obj = cache_pop(co->cache_cache); + } + + /* Cache is empty, free the cache table */ + cache_free(co->cache_cache); + + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + return APR_SUCCESS; +} +/* + * TODO: enable directives to be overridden in various containers + */ +static void *create_cache_config(apr_pool_t *p, server_rec *s) +{ + sconf = apr_pcalloc(p, sizeof(mem_cache_conf)); + + sconf->min_cache_object_size = DEFAULT_MIN_CACHE_OBJECT_SIZE; + sconf->max_cache_object_size = DEFAULT_MAX_CACHE_OBJECT_SIZE; + /* Number of objects in the cache */ + sconf->max_object_cnt = DEFAULT_MAX_OBJECT_CNT; + /* Size of the cache in bytes */ + sconf->max_cache_size = DEFAULT_MAX_CACHE_SIZE; + sconf->cache_cache = NULL; + sconf->cache_remove_algorithm = memcache_gdsf_algorithm; + sconf->max_streaming_buffer_size = DEFAULT_MAX_STREAMING_BUFFER_SIZE; + + return sconf; +} + +static int create_entity(cache_handle_t *h, cache_type_e type_e, + request_rec *r, const char *key, apr_off_t len) +{ + cache_object_t *obj, *tmp_obj; + mem_cache_object_t *mobj; + apr_size_t key_len; + + if (len == -1) { + /* Caching a streaming response. Assume the response is + * less than or equal to max_streaming_buffer_size. We will + * correct all the cache size counters in store_body once + * we know exactly know how much we are caching. + */ + len = sconf->max_streaming_buffer_size; + } + + /* Note: cache_insert() will automatically garbage collect + * objects from the cache if the max_cache_size threshold is + * exceeded. This means mod_mem_cache does not need to implement + * max_cache_size checks. + */ + if (len < sconf->min_cache_object_size || + len > sconf->max_cache_object_size) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mem_cache: URL %s failed the size check and will not be cached.", + key); + return DECLINED; + } + + if (type_e == CACHE_TYPE_FILE) { + /* CACHE_TYPE_FILE is only valid for local content handled by the + * default handler. Need a better way to check if the file is + * local or not. + */ + if (!r->filename) { + return DECLINED; + } + } + + /* Allocate and initialize cache_object_t */ + obj = calloc(1, sizeof(*obj)); + if (!obj) { + return DECLINED; + } + key_len = strlen(key) + 1; + obj->key = malloc(key_len); + if (!obj->key) { + cleanup_cache_object(obj); + return DECLINED; + } + memcpy((void*)obj->key, key, key_len); + + /* Allocate and init mem_cache_object_t */ + mobj = calloc(1, sizeof(*mobj)); + if (!mobj) { + cleanup_cache_object(obj); + return DECLINED; + } + + /* Finish initing the cache object */ + apr_atomic_set32(&obj->refcount, 1); + mobj->total_refs = 1; + obj->complete = 0; + obj->vobj = mobj; + /* Safe cast: We tested < sconf->max_cache_object_size above */ + mobj->m_len = (apr_size_t)len; + mobj->type = type_e; + + /* Place the cache_object_t into the hash table. + * Note: Perhaps we should wait to put the object in the + * hash table when the object is complete? I add the object here to + * avoid multiple threads attempting to cache the same content only + * to discover at the very end that only one of them will succeed. + * Furthermore, adding the cache object to the table at the end could + * open up a subtle but easy to exploit DoS hole: someone could request + * a very large file with multiple requests. Better to detect this here + * rather than after the cache object has been completely built and + * initialized... + * XXX Need a way to insert into the cache w/o such coarse grained locking + */ + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + tmp_obj = (cache_object_t *) cache_find(sconf->cache_cache, key); + + if (!tmp_obj) { + cache_insert(sconf->cache_cache, obj); + /* Add a refcount to account for the reference by the + * hashtable in the cache. Refcount should be 2 now, one + * for this thread, and one for the cache. + */ + apr_atomic_inc32(&obj->refcount); + } + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + + if (tmp_obj) { + /* This thread collided with another thread loading the same object + * into the cache at the same time. Defer to the other thread which + * is further along. + */ + cleanup_cache_object(obj); + return DECLINED; + } + + apr_pool_cleanup_register(r->pool, obj, decrement_refcount, + apr_pool_cleanup_null); + + /* Populate the cache handle */ + h->cache_obj = obj; + + return OK; +} + +static int create_mem_entity(cache_handle_t *h, request_rec *r, + const char *key, apr_off_t len) +{ + return create_entity(h, CACHE_TYPE_HEAP, r, key, len); +} + +static int create_fd_entity(cache_handle_t *h, request_rec *r, + const char *key, apr_off_t len) +{ + return create_entity(h, CACHE_TYPE_FILE, r, key, len); +} + +static int open_entity(cache_handle_t *h, request_rec *r, const char *key) +{ + cache_object_t *obj; + + /* Look up entity keyed to 'url' */ + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + obj = (cache_object_t *) cache_find(sconf->cache_cache, key); + if (obj) { + if (obj->complete) { + request_rec *rmain=r, *rtmp; + apr_atomic_inc32(&obj->refcount); + /* cache is worried about overall counts, not 'open' ones */ + cache_update(sconf->cache_cache, obj); + + /* If this is a subrequest, register the cleanup against + * the main request. This will prevent the cache object + * from being cleaned up from under the request after the + * subrequest is destroyed. + */ + rtmp = r; + while (rtmp) { + rmain = rtmp; + rtmp = rmain->main; + } + apr_pool_cleanup_register(rmain->pool, obj, decrement_refcount, + apr_pool_cleanup_null); + } + else { + obj = NULL; + } + } + + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + + if (!obj) { + return DECLINED; + } + + /* Initialize the cache_handle */ + h->cache_obj = obj; + h->req_hdrs = NULL; /* Pick these up in recall_headers() */ + return OK; +} + +/* remove_entity() + * Notes: + * refcount should be at least 1 upon entry to this function to account + * for this thread's reference to the object. If the refcount is 1, then + * object has been removed from the cache by another thread and this thread + * is the last thread accessing the object. + */ +static int remove_entity(cache_handle_t *h) +{ + cache_object_t *obj = h->cache_obj; + cache_object_t *tobj = NULL; + + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + + /* If the entity is still in the cache, remove it and decrement the + * refcount. If the entity is not in the cache, do nothing. In both cases + * decrement_refcount called by the last thread referencing the object will + * trigger the cleanup. + */ + tobj = cache_find(sconf->cache_cache, obj->key); + if (tobj == obj) { + cache_remove(sconf->cache_cache, obj); + apr_atomic_dec32(&obj->refcount); + } + + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + + return OK; +} +static apr_status_t serialize_table(cache_header_tbl_t **obj, + apr_ssize_t *nelts, + apr_table_t *table) +{ + const apr_array_header_t *elts_arr = apr_table_elts(table); + apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts; + apr_ssize_t i; + apr_size_t len = 0; + apr_size_t idx = 0; + char *buf; + + *nelts = elts_arr->nelts; + if (*nelts == 0 ) { + *obj=NULL; + return APR_SUCCESS; + } + *obj = malloc(sizeof(cache_header_tbl_t) * elts_arr->nelts); + if (NULL == *obj) { + return APR_ENOMEM; + } + for (i = 0; i < elts_arr->nelts; ++i) { + len += strlen(elts[i].key); + len += strlen(elts[i].val); + len += 2; /* Extra space for NULL string terminator for key and val */ + } + + /* Transfer the headers into a contiguous memory block */ + buf = malloc(len); + if (!buf) { + *obj = NULL; + return APR_ENOMEM; + } + + for (i = 0; i < *nelts; ++i) { + (*obj)[i].hdr = &buf[idx]; + len = strlen(elts[i].key) + 1; /* Include NULL terminator */ + memcpy(&buf[idx], elts[i].key, len); + idx+=len; + + (*obj)[i].val = &buf[idx]; + len = strlen(elts[i].val) + 1; + memcpy(&buf[idx], elts[i].val, len); + idx+=len; + } + return APR_SUCCESS; +} +static int unserialize_table( cache_header_tbl_t *ctbl, + int num_headers, + apr_table_t *t ) +{ + int i; + + for (i = 0; i < num_headers; ++i) { + apr_table_addn(t, ctbl[i].hdr, ctbl[i].val); + } + + return APR_SUCCESS; +} +/* Define request processing hook handlers */ +/* remove_url() + * Notes: + */ +static int remove_url(cache_handle_t *h, apr_pool_t *p) +{ + cache_object_t *obj; + int cleanup = 0; + + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + + obj = h->cache_obj; + if (obj) { + cache_remove(sconf->cache_cache, obj); + /* For performance, cleanup cache object after releasing the lock */ + cleanup = !apr_atomic_dec32(&obj->refcount); + } + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + + if (cleanup) { + cleanup_cache_object(obj); + } + + return OK; +} + +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) +{ + int rc; + mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj; + + h->req_hdrs = apr_table_make(r->pool, mobj->num_req_hdrs); + h->resp_hdrs = apr_table_make(r->pool, mobj->num_header_out); + + rc = unserialize_table(mobj->req_hdrs, mobj->num_req_hdrs, h->req_hdrs); + rc = unserialize_table(mobj->header_out, mobj->num_header_out, + h->resp_hdrs); + + return rc; +} + +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb) +{ + apr_bucket *b; + mem_cache_object_t *mobj = (mem_cache_object_t*) h->cache_obj->vobj; + + if (mobj->type == CACHE_TYPE_FILE) { + /* CACHE_TYPE_FILE */ + apr_file_t *file; + apr_os_file_put(&file, &mobj->fd, mobj->flags, p); + b = apr_bucket_file_create(file, 0, mobj->m_len, p, bb->bucket_alloc); + } + else { + /* CACHE_TYPE_HEAP */ + b = apr_bucket_immortal_create(mobj->m, mobj->m_len, bb->bucket_alloc); + } + APR_BRIGADE_INSERT_TAIL(bb, b); + b = apr_bucket_eos_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + + return APR_SUCCESS; +} + + +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info) +{ + cache_object_t *obj = h->cache_obj; + mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj; + int rc; + apr_table_t *headers_out; + + /* + * The cache needs to keep track of the following information: + * - Date, LastMod, Version, ReqTime, RespTime, ContentLength + * - The original request headers (for Vary) + * - The original response headers (for returning with a cached response) + * - The body of the message + */ + rc = serialize_table(&mobj->req_hdrs, + &mobj->num_req_hdrs, + r->headers_in); + if (rc != APR_SUCCESS) { + return rc; + } + + /* Precompute how much storage we need to hold the headers */ + headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out, + r->server); + + /* If not set in headers_out, set Content-Type */ + if (!apr_table_get(headers_out, "Content-Type") + && r->content_type) { + apr_table_setn(headers_out, "Content-Type", + ap_make_content_type(r, r->content_type)); + } + + headers_out = apr_table_overlay(r->pool, headers_out, r->err_headers_out); + + rc = serialize_table(&mobj->header_out, &mobj->num_header_out, + headers_out); + if (rc != APR_SUCCESS) { + return rc; + } + + /* Init the info struct */ + obj->info.status = info->status; + if (info->date) { + obj->info.date = info->date; + } + if (info->response_time) { + obj->info.response_time = info->response_time; + } + if (info->request_time) { + obj->info.request_time = info->request_time; + } + if (info->expire) { + obj->info.expire = info->expire; + } + + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b) +{ + apr_status_t rv; + cache_object_t *obj = h->cache_obj; + cache_object_t *tobj = NULL; + mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj; + apr_read_type_e eblock = APR_BLOCK_READ; + apr_bucket *e; + char *cur; + int eos = 0; + + if (mobj->type == CACHE_TYPE_FILE) { + apr_file_t *file = NULL; + int fd = 0; + int other = 0; + + /* We can cache an open file descriptor if: + * - the brigade contains one and only one file_bucket && + * - the brigade is complete && + * - the file_bucket is the last data bucket in the brigade + */ + for (e = APR_BRIGADE_FIRST(b); + e != APR_BRIGADE_SENTINEL(b); + e = APR_BUCKET_NEXT(e)) + { + if (APR_BUCKET_IS_EOS(e)) { + eos = 1; + } + else if (APR_BUCKET_IS_FILE(e)) { + apr_bucket_file *a = e->data; + fd++; + file = a->fd; + } + else { + other++; + } + } + if (fd == 1 && !other && eos) { + apr_file_t *tmpfile; + const char *name; + /* Open a new XTHREAD handle to the file */ + apr_file_name_get(&name, file); + mobj->flags = ((APR_SENDFILE_ENABLED & apr_file_flags_get(file)) + | APR_READ | APR_BINARY | APR_XTHREAD | APR_FILE_NOCLEANUP); + rv = apr_file_open(&tmpfile, name, mobj->flags, + APR_OS_DEFAULT, r->pool); + if (rv != APR_SUCCESS) { + return rv; + } + apr_file_inherit_unset(tmpfile); + apr_os_file_get(&(mobj->fd), tmpfile); + + /* Open for business */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, + "mem_cache: Cached file: %s with key: %s", name, obj->key); + obj->complete = 1; + return APR_SUCCESS; + } + + /* Content not suitable for fd caching. Cache in-memory instead. */ + mobj->type = CACHE_TYPE_HEAP; + } + + /* + * FD cacheing is not enabled or the content was not + * suitable for fd caching. + */ + if (mobj->m == NULL) { + mobj->m = malloc(mobj->m_len); + if (mobj->m == NULL) { + return APR_ENOMEM; + } + obj->count = 0; + } + cur = (char*) mobj->m + obj->count; + + /* Iterate accross the brigade and populate the cache storage */ + for (e = APR_BRIGADE_FIRST(b); + e != APR_BRIGADE_SENTINEL(b); + e = APR_BUCKET_NEXT(e)) + { + const char *s; + apr_size_t len; + + if (APR_BUCKET_IS_EOS(e)) { + if (mobj->m_len > obj->count) { + /* Caching a streamed response. Reallocate a buffer of the + * correct size and copy the streamed response into that + * buffer */ + char *buf = malloc(obj->count); + if (!buf) { + return APR_ENOMEM; + } + memcpy(buf, mobj->m, obj->count); + free(mobj->m); + mobj->m = buf; + + /* Now comes the crufty part... there is no way to tell the + * cache that the size of the object has changed. We need + * to remove the object, update the size and re-add the + * object, all under protection of the lock. + */ + if (sconf->lock) { + apr_thread_mutex_lock(sconf->lock); + } + /* Has the object been ejected from the cache? + */ + tobj = (cache_object_t *) cache_find(sconf->cache_cache, obj->key); + if (tobj == obj) { + /* Object is still in the cache, remove it, update the len field then + * replace it under protection of sconf->lock. + */ + cache_remove(sconf->cache_cache, obj); + /* For illustration, cache no longer has reference to the object + * so decrement the refcount + * apr_atomic_dec32(&obj->refcount); + */ + mobj->m_len = obj->count; + + cache_insert(sconf->cache_cache, obj); + /* For illustration, cache now has reference to the object, so + * increment the refcount + * apr_atomic_inc32(&obj->refcount); + */ + } + else if (tobj) { + /* Different object with the same key found in the cache. Doing nothing + * here will cause the object refcount to drop to 0 in decrement_refcount + * and the object will be cleaned up. + */ + + } else { + /* Object has been ejected from the cache, add it back to the cache */ + mobj->m_len = obj->count; + cache_insert(sconf->cache_cache, obj); + apr_atomic_inc32(&obj->refcount); + } + + if (sconf->lock) { + apr_thread_mutex_unlock(sconf->lock); + } + } + /* Open for business */ + ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, + "mem_cache: Cached url: %s", obj->key); + obj->complete = 1; + break; + } + rv = apr_bucket_read(e, &s, &len, eblock); + if (rv != APR_SUCCESS) { + return rv; + } + if (len) { + /* Check for buffer overflow */ + if ((obj->count + len) > mobj->m_len) { + return APR_ENOMEM; + } + else { + memcpy(cur, s, len); + cur+=len; + obj->count+=len; + } + } + /* This should not fail, but if it does, we are in BIG trouble + * cause we just stomped all over the heap. + */ + AP_DEBUG_ASSERT(obj->count <= mobj->m_len); + } + return APR_SUCCESS; +} +/** + * Configuration and start-up + */ +static int mem_cache_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + int threaded_mpm; + + /* Sanity check the cache configuration */ + if (sconf->min_cache_object_size >= sconf->max_cache_object_size) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, + "MCacheMaxObjectSize must be greater than MCacheMinObjectSize"); + return DONE; + } + if (sconf->max_cache_object_size >= sconf->max_cache_size) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, + "MCacheSize must be greater than MCacheMaxObjectSize"); + return DONE; + } + if (sconf->max_streaming_buffer_size > sconf->max_cache_object_size) { + /* Issue a notice only if something other than the default config + * is being used */ + if (sconf->max_streaming_buffer_size != DEFAULT_MAX_STREAMING_BUFFER_SIZE && + sconf->max_cache_object_size != DEFAULT_MAX_CACHE_OBJECT_SIZE) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, + "MCacheMaxStreamingBuffer must be less than or equal to MCacheMaxObjectSize. " + "Resetting MCacheMaxStreamingBuffer to MCacheMaxObjectSize."); + } + sconf->max_streaming_buffer_size = sconf->max_cache_object_size; + } + if (sconf->max_streaming_buffer_size < sconf->min_cache_object_size) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, + "MCacheMaxStreamingBuffer must be greater than or equal to MCacheMinObjectSize. " + "Resetting MCacheMaxStreamingBuffer to MCacheMinObjectSize."); + sconf->max_streaming_buffer_size = sconf->min_cache_object_size; + } + ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm); + if (threaded_mpm) { + apr_thread_mutex_create(&sconf->lock, APR_THREAD_MUTEX_DEFAULT, p); + } + + sconf->cache_cache = cache_init(sconf->max_object_cnt, + sconf->max_cache_size, + memcache_get_priority, + sconf->cache_remove_algorithm, + memcache_get_pos, + memcache_set_pos, + memcache_inc_frequency, + memcache_cache_get_size, + memcache_cache_get_key, + memcache_cache_free); + apr_pool_cleanup_register(p, sconf, cleanup_cache_mem, apr_pool_cleanup_null); + + if (sconf->cache_cache) + return OK; + + return -1; + +} + +static const char +*set_max_cache_size(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + apr_size_t val; + + if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { + return "MCacheSize argument must be an integer representing the max cache size in KBytes."; + } + sconf->max_cache_size = val*1024; + return NULL; +} +static const char +*set_min_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + apr_size_t val; + + if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { + return "MCacheMinObjectSize value must be an integer (bytes)"; + } + sconf->min_cache_object_size = val; + return NULL; +} +static const char +*set_max_cache_object_size(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + apr_size_t val; + + if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { + return "MCacheMaxObjectSize value must be an integer (bytes)"; + } + sconf->max_cache_object_size = val; + return NULL; +} +static const char +*set_max_object_count(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + apr_size_t val; + + if (sscanf(arg, "%" APR_SIZE_T_FMT, &val) != 1) { + return "MCacheMaxObjectCount value must be an integer"; + } + sconf->max_object_cnt = val; + return NULL; +} + +static const char +*set_cache_removal_algorithm(cmd_parms *parms, void *name, const char *arg) +{ + if (strcasecmp("LRU", arg)) { + sconf->cache_remove_algorithm = memcache_lru_algorithm; + } + else { + if (strcasecmp("GDSF", arg)) { + sconf->cache_remove_algorithm = memcache_gdsf_algorithm; + } + else { + return "currently implemented algorithms are LRU and GDSF"; + } + } + return NULL; +} + +static const char *set_max_streaming_buffer(cmd_parms *parms, void *dummy, + const char *arg) +{ + char *err; + if (apr_strtoff(&sconf->max_streaming_buffer_size, arg, &err, 10) || *err) { + return "MCacheMaxStreamingBuffer value must be a number"; + } + + return NULL; +} + +static const command_rec cache_cmds[] = +{ + AP_INIT_TAKE1("MCacheSize", set_max_cache_size, NULL, RSRC_CONF, + "The maximum amount of memory used by the cache in KBytes"), + AP_INIT_TAKE1("MCacheMaxObjectCount", set_max_object_count, NULL, RSRC_CONF, + "The maximum number of objects allowed to be placed in the cache"), + AP_INIT_TAKE1("MCacheMinObjectSize", set_min_cache_object_size, NULL, RSRC_CONF, + "The minimum size (in bytes) of an object to be placed in the cache"), + AP_INIT_TAKE1("MCacheMaxObjectSize", set_max_cache_object_size, NULL, RSRC_CONF, + "The maximum size (in bytes) of an object to be placed in the cache"), + AP_INIT_TAKE1("MCacheRemovalAlgorithm", set_cache_removal_algorithm, NULL, RSRC_CONF, + "The algorithm used to remove entries from the cache (default: GDSF)"), + AP_INIT_TAKE1("MCacheMaxStreamingBuffer", set_max_streaming_buffer, NULL, RSRC_CONF, + "Maximum number of bytes of content to buffer for a streamed response"), + {NULL} +}; + +static const cache_provider cache_mem_provider = +{ + &remove_entity, + &store_headers, + &store_body, + &recall_headers, + &recall_body, + &create_mem_entity, + &open_entity, + &remove_url, +}; + +static const cache_provider cache_fd_provider = +{ + &remove_entity, + &store_headers, + &store_body, + &recall_headers, + &recall_body, + &create_fd_entity, + &open_entity, + &remove_url, +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_hook_post_config(mem_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE); + /* cache initializer */ + /* cache_hook_init(cache_mem_init, NULL, NULL, APR_HOOK_MIDDLE); */ + /* + cache_hook_create_entity(create_entity, NULL, NULL, APR_HOOK_MIDDLE); + cache_hook_open_entity(open_entity, NULL, NULL, APR_HOOK_MIDDLE); + cache_hook_remove_url(remove_url, NULL, NULL, APR_HOOK_MIDDLE); + */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "mem", "0", + &cache_mem_provider); + ap_register_provider(p, CACHE_PROVIDER_GROUP, "fd", "0", + &cache_fd_provider); +} + +module AP_MODULE_DECLARE_DATA mem_cache_module = +{ + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + create_cache_config, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + cache_cmds, /* command apr_table_t */ + register_hooks +}; diff --git a/modules/cache/mod_mem_cache.dsp b/modules/cache/mod_mem_cache.dsp new file mode 100644 index 00000000..7b77743f --- /dev/null +++ b/modules/cache/mod_mem_cache.dsp @@ -0,0 +1,103 @@ +# Microsoft Developer Studio Project File - Name="mod_mem_cache" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mod_mem_cache - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mod_mem_cache.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mod_mem_cache.mak" CFG="mod_mem_cache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_mem_cache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_mem_cache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mod_mem_cache - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "mod_mem_cache_EXPORTS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_mem_cache_src" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /fo"Release/mod_mem_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d "BIN_NAME=mod_mem_cache.so" /d "LONG_NAME=mem_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Release/mod_mem_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mem_cache.so /opt:ref + +!ELSEIF "$(CFG)" == "mod_mem_cache - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_mem_cache_src" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /fo"Debug/mod_mem_cache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d "BIN_NAME=mod_mem_cache.so" /d "LONG_NAME=mem_cache_module for Apache" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug +# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:"Debug/mod_mem_cache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_mem_cache.so + +!ENDIF + +# Begin Target + +# Name "mod_mem_cache - Win32 Release" +# Name "mod_mem_cache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_mem_cache.c +# End Source File +# Begin Source File + +SOURCE=..\..\build\win32\httpd.rc +# End Source File +# End Target +# End Project |