diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
commit | 5dff2d61cc1c27747ee398e04d8e02843aabb1f8 (patch) | |
tree | a67c336b406c8227bac912beb74a1ad3cdc55100 /modules/cache | |
parent | Initial commit. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.38.upstream/2.4.38upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/cache')
52 files changed, 18015 insertions, 0 deletions
diff --git a/modules/cache/.indent.pro b/modules/cache/.indent.pro new file mode 100644 index 0000000..a9fbe9f --- /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 0000000..167b343 --- /dev/null +++ b/modules/cache/Makefile.in @@ -0,0 +1,3 @@ + +include $(top_srcdir)/build/special.mk + diff --git a/modules/cache/NWGNUcach_dsk b/modules/cache/NWGNUcach_dsk new file mode 100644 index 0000000..1c45f34 --- /dev/null +++ b/modules/cache/NWGNUcach_dsk @@ -0,0 +1,262 @@ +# +# 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)/server/mpm/netware \ + $(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 = cach_dsk + +# +# 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 = $(NLM_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 = 65536 + + +# +# 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 this is 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 = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).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_cache_disk.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 = \ + $(PRELUDE) \ + $(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 \ + @aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + cache_disk_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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUcach_socache b/modules/cache/NWGNUcach_socache new file mode 100644 index 0000000..68b3cd9 --- /dev/null +++ b/modules/cache/NWGNUcach_socache @@ -0,0 +1,263 @@ +# +# 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 \ + $(SRC)/include \ + $(STDMOD)/generators \ + $(SERVER)/mpm/netware \ + $(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 = cach_socache + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Cache Socache Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = cach_socache + +# +# 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 = + +# +# 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 this is 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 = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).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_cache_socache.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 = \ + $(PRELUDE) \ + $(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 \ + @aprlib.imp \ + @httpd.imp \ + @mod_cache.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + cache_socache_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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUmakefile b/modules/cache/NWGNUmakefile new file mode 100644 index 0000000..e544df6 --- /dev/null +++ b/modules/cache/NWGNUmakefile @@ -0,0 +1,250 @@ +# +# 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)/cach_dsk.nlm \ + $(OBJDIR)/cach_socache.nlm \ + $(OBJDIR)/socachdbm.nlm \ + $(OBJDIR)/socachmem.nlm \ + $(OBJDIR)/socachshmcb.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 + $(call COPY,$(OBJDIR)/*.nlm, $(INSTALLBASE)/modules/) + +# +# Any specialized rules here +# + +# +# Include the 'tail' makefile that has targets that depend on variables defined +# in this makefile +# + +include $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUmod_cach b/modules/cache/NWGNUmod_cach new file mode 100644 index 0000000..b17b6b6 --- /dev/null +++ b/modules/cache/NWGNUmod_cach @@ -0,0 +1,265 @@ +# +# 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)/server/mpm/netware \ + $(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 = + +# +# 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 this is 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 = + +# +# 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) + +# +# 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 = \ + $(PRELUDE) \ + $(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 \ + @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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUsocachdbm b/modules/cache/NWGNUsocachdbm new file mode 100644 index 0000000..f673924 --- /dev/null +++ b/modules/cache/NWGNUsocachdbm @@ -0,0 +1,261 @@ +# +# 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)/server/mpm/netware \ + $(NWOS) \ + $(STDMOD)/generators \ + $(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 = socachedbm + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Socache DBM Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = socachedbm + +# +# 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 = + +# +# 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 this is 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 = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).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_socache_dbm.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 = \ + $(PRELUDE) \ + $(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 \ + @aprlib.imp \ + @httpd.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + socache_dbm_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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUsocachmem b/modules/cache/NWGNUsocachmem new file mode 100644 index 0000000..cfbf815 --- /dev/null +++ b/modules/cache/NWGNUsocachmem @@ -0,0 +1,261 @@ +# +# 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 \ + $(STDMOD)/generators \ + $(AP_WORK)/server/mpm/netware \ + $(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 = socachemem + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Socache Memory Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = socachemem + +# +# 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 = + +# +# 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 this is 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 = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).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_socache_memcache.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 = \ + $(PRELUDE) \ + $(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 \ + @aprlib.imp \ + @httpd.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + socache_memcache_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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/NWGNUsocachshmcb b/modules/cache/NWGNUsocachshmcb new file mode 100644 index 0000000..bc1850e --- /dev/null +++ b/modules/cache/NWGNUsocachshmcb @@ -0,0 +1,261 @@ +# +# 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)/server/mpm/netware \ + $(NWOS) \ + $(STDMOD)/generators \ + $(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 = socacheshmcb + +# +# This is used by the link '-desc ' directive. +# If left blank, NLM_NAME will be used. +# +NLM_DESCRIPTION = Apache $(VERSION_STR) Socache DC Module + +# +# This is used by the '-threadname' directive. If left blank, +# NLM_NAME Thread will be used. +# +NLM_THREAD_NAME = socacheshmcb + +# +# 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 = + +# +# 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 this is 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 = + +# +# Declare all target files (you must add your files here) +# + +# +# If there is an NLM target, put it here +# +TARGET_nlm = \ + $(OBJDIR)/$(NLM_NAME).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_socache_shmcb.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 = \ + $(PRELUDE) \ + $(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 \ + @aprlib.imp \ + @httpd.imp \ + $(EOLIST) + +# +# Any symbols exported to here +# +FILES_nlm_exports = \ + socache_shmcb_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 $(APBUILD)/NWGNUtail.inc + + diff --git a/modules/cache/cache_common.h b/modules/cache/cache_common.h new file mode 100644 index 0000000..9d56d28 --- /dev/null +++ b/modules/cache/cache_common.h @@ -0,0 +1,56 @@ +/* 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_common.h + * @brief Common Cache structs + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_COMMON_H +#define CACHE_COMMON_H + +/* a cache control header breakdown */ +typedef struct cache_control { + unsigned int parsed:1; + unsigned int cache_control:1; + unsigned int pragma:1; + unsigned int no_cache:1; + unsigned int no_cache_header:1; /* no cache by header match */ + unsigned int no_store:1; + unsigned int max_age:1; + unsigned int max_stale:1; + unsigned int min_fresh:1; + unsigned int no_transform:1; + unsigned int only_if_cached:1; + unsigned int public:1; + unsigned int private:1; + unsigned int private_header:1; /* private by header match */ + unsigned int must_revalidate:1; + unsigned int proxy_revalidate:1; + unsigned int s_maxage:1; + unsigned int invalidated:1; /* has this entity been invalidated? */ + apr_int64_t max_age_value; /* if positive, then set */ + apr_int64_t max_stale_value; /* if positive, then set */ + apr_int64_t min_fresh_value; /* if positive, then set */ + apr_int64_t s_maxage_value; /* if positive, then set */ +} cache_control_t; + +#endif /* CACHE_COMMON_H */ +/** @} */ diff --git a/modules/cache/cache_disk_common.h b/modules/cache/cache_disk_common.h new file mode 100644 index 0000000..ace569e --- /dev/null +++ b/modules/cache/cache_disk_common.h @@ -0,0 +1,68 @@ +/* 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_disk_common.h + * @brief Common Disk Cache vars/structs + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_DISK_CACHE + * @{ + */ + +#ifndef CACHE_DIST_COMMON_H +#define CACHE_DIST_COMMON_H + +#define VARY_FORMAT_VERSION 5 +#define DISK_FORMAT_VERSION 6 + +#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; + /* The ident of the body file, so we can test the body matches the header */ + apr_ino_t inode; + apr_dev_t device; + /* Does this cached request have a body? */ + unsigned int has_body:1; + unsigned int header_only:1; + /* The parsed cache control header */ + cache_control_t control; +} disk_cache_info_t; + +#endif /* CACHE_DIST_COMMON_H */ +/** @} */ diff --git a/modules/cache/cache_socache_common.h b/modules/cache/cache_socache_common.h new file mode 100644 index 0000000..3ee3d0d --- /dev/null +++ b/modules/cache/cache_socache_common.h @@ -0,0 +1,57 @@ +/* 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_socache_common.h + * @brief Common Shared Object Cache vars/structs + * + * @defgroup Cache_cache Cache Functions + * @ingroup MOD_SOCACHE_CACHE + * @{ + */ + +#ifndef CACHE_SOCACHE_COMMON_H +#define CACHE_SOCACHE_COMMON_H + +#include "apr_time.h" + +#include "cache_common.h" + +#define CACHE_SOCACHE_VARY_FORMAT_VERSION 1 +#define CACHE_SOCACHE_DISK_FORMAT_VERSION 2 + +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; + /* Does this cached request have a body? */ + unsigned int header_only:1; + /* The parsed cache control header */ + cache_control_t control; +} cache_socache_info_t; + +#endif /* CACHE_SOCACHE_COMMON_H */ +/** @} */ diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c new file mode 100644 index 0000000..41f638c --- /dev/null +++ b/modules/cache/cache_storage.c @@ -0,0 +1,791 @@ +/* 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 "mod_cache.h" + +#include "cache_storage.h" +#include "cache_util.h" + +APLOG_USE_MODULE(cache); + +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, request_rec *r) +{ + 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-stale handle. + */ + h = cache->stale_handle ? cache->stale_handle : cache->handle; + if (!h) { + return OK; + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00691) + "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, r); + 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(cache_request_rec *cache, request_rec *r, + apr_off_t size, apr_bucket_brigade *in) +{ + cache_provider_list *list; + cache_handle_t *h = apr_pcalloc(r->pool, sizeof(cache_handle_t)); + apr_status_t rv; + + if (!cache) { + /* This should never happen */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00692) + "cache: No cache request information available for key" + " generation"); + return APR_EGENERAL; + } + + if (!cache->key) { + rv = cache_generate_key(r, r->pool, &cache->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, cache->key, size, in)) { + 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 filter_header_do(void *v, const char *key, const char *val) +{ + if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning") + && *val == '1') { + /* any stored Warning headers with warn-code 1xx (see section + * 14.46) MUST be deleted from the cache entry and the forwarded + * response. + */ + } + else { + apr_table_addn(v, key, val); + } + return 1; +} +static int remove_header_do(void *v, const char *key, const char *val) +{ + if ((*key == 'W' || *key == 'w') && !ap_cstr_casecmp(key, "Warning")) { + /* any stored Warning headers with warn-code 2xx MUST be retained + * in the cache entry and the forwarded response. + */ + } + else { + apr_table_unset(v, key); + } + return 1; +} +static int add_header_do(void *v, const char *key, const char *val) +{ + apr_table_addn(v, key, val); + return 1; +} + +/** + * Take two sets of headers, sandwich them together, and apply the result to + * r->headers_out. + * + * To complicate this, a header may be duplicated in either table. Should a + * header exist in the top table, all matching headers will be removed from + * the bottom table before the headers are combined. The Warning headers are + * handled specially. Warnings are added rather than being replaced, while + * in the case of revalidation 1xx Warnings are stripped. + * + * The Content-Type and Last-Modified headers are then re-parsed and inserted + * into the request. + */ +void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top, + apr_table_t *bottom, int revalidation) +{ + const char *v; + + if (revalidation) { + r->headers_out = apr_table_make(r->pool, 10); + apr_table_do(filter_header_do, r->headers_out, bottom, NULL); + } + else if (r->headers_out != bottom) { + r->headers_out = apr_table_copy(r->pool, bottom); + } + apr_table_do(remove_header_do, r->headers_out, top, NULL); + apr_table_do(add_header_do, r->headers_out, top, NULL); + + v = apr_table_get(r->headers_out, "Content-Type"); + if (v) { + ap_set_content_type(r, v); + /* + * 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(r->headers_out, "Last-Modified"); + if (v) { + ap_update_mtime(r, apr_date_parse_http(v)); + ap_set_last_modified(r); + } + +} + +/* + * 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(cache_request_rec *cache, request_rec *r) +{ + cache_provider_list *list; + apr_status_t rv; + cache_handle_t *h; + + if (!cache) { + /* This should never happen */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00693) + "cache: No cache request information available for key" + " generation"); + return DECLINED; + } + + /* if no-cache, we can't serve from the cache, but we may store to the + * cache. + */ + if (!ap_cache_check_no_cache(cache, r)) { + return DECLINED; + } + + if (!cache->key) { + rv = cache_generate_key(r, r->pool, &cache->key); + if (rv != APR_SUCCESS) { + return DECLINED; + } + } + + /* 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, cache->key))) { + case OK: { + char *vary = NULL; + int mismatch = 0; + char *last = NULL; + + if (list->provider->recall_headers(h, r) != APR_SUCCESS) { + /* try again with next cache type */ + list = list->next; + continue; + } + + /* + * 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 = cache_strqtok( + apr_pstrdup(r->pool, + cache_table_getm(r->pool, h->resp_hdrs, "Vary")), + CACHE_SEPARATOR, &last); + while (vary) { + const char *h1, *h2; + + /* + * 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 = cache_table_getm(r->pool, r->headers_in, vary); + h2 = cache_table_getm(r->pool, h->req_hdrs, vary); + 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_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00694) "cache_select(): Vary header mismatch."); + mismatch = 1; + break; + } + vary = cache_strqtok(NULL, CACHE_SEPARATOR, &last); + } + + /* no vary match, try next provider */ + if (mismatch) { + /* try again with next cache type */ + list = list->next; + continue; + } + + cache->provider = list->provider; + cache->provider_name = list->provider_name; + + /* + * RFC2616 13.3.4 Rules for When to Use Entity Tags and Last-Modified + * Dates: An HTTP/1.1 caching proxy, upon receiving a conditional request + * that includes both a Last-Modified date and one or more entity tags as + * cache validators, MUST NOT return a locally cached response to the + * client unless that cached response is consistent with all of the + * conditional header fields in the request. + */ + if (ap_condition_if_match(r, h->resp_hdrs) == AP_CONDITION_NOMATCH + || ap_condition_if_unmodified_since(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_none_match(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_modified_since(r, h->resp_hdrs) + == AP_CONDITION_NOMATCH + || ap_condition_if_range(r, h->resp_hdrs) == AP_CONDITION_NOMATCH) { + mismatch = 1; + } + + /* Is our cached response fresh enough? */ + if (mismatch || !cache_check_freshness(h, cache, r)) { + const char *etag, *lastmod; + + /* Cache-Control: only-if-cached and revalidation required, try + * the next provider + */ + if (cache->control_in.only_if_cached) { + /* try again with next cache type */ + list = list->next; + continue; + } + + /* set aside the stale entry for accessing later */ + cache->stale_headers = apr_table_copy(r->pool, + r->headers_in); + cache->stale_handle = h; + + /* if no existing conditionals, use conditionals of our own */ + if (!mismatch) { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00695) "Cached response for %s isn't fresh. Adding " + "conditional request headers.", r->uri); + + /* Remove existing conditionals that might conflict with ours */ + 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); + } + + /* + * Do not do Range requests with our own conditionals: If + * we get 304 the Range does not matter and otherwise the + * entity changed and we want to have the complete entity + */ + apr_table_unset(r->headers_in, "Range"); + + } + + } + + /* ready to revalidate, pretend we were never here */ + return DECLINED; + } + + /* Okay, this response looks okay. Merge in our stuff and go. */ + cache_accept_headers(h, r, h->resp_hdrs, r->headers_out, 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; + } + } + } + + /* if Cache-Control: only-if-cached, and not cached, return 504 */ + if (cache->control_in.only_if_cached) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00696) + "cache: 'only-if-cached' requested and no cached entity, " + "returning 504 Gateway Timeout for: %s", r->uri); + return HTTP_GATEWAY_TIME_OUT; + } + + return DECLINED; +} + +static apr_status_t cache_canonicalise_key(request_rec *r, apr_pool_t* p, + const char *path, const char *query, + apr_uri_t *parsed_uri, + const char **key) +{ + cache_server_conf *conf; + char *port_str, *hn, *lcs; + const char *hostname, *scheme; + int i; + const char *kpath; + const char *kquery; + + if (*key) { + /* + * We have been here before during the processing of this request. + */ + return APR_SUCCESS; + } + + /* + * Get the module configuration. We need this for the CacheIgnoreQueryString + * option below. + */ + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * 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)) { + if (conf->base_uri && conf->base_uri->hostname) { + hostname = conf->base_uri->hostname; + } + else { + /* Use _default_ as the hostname if none present, as in mod_vhost */ + hostname = ap_get_server_name(r); + if (!hostname) { + hostname = "_default_"; + } + } + } + else if (parsed_uri->hostname) { + /* Copy the parsed uri hostname */ + hn = apr_pstrdup(p, 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 && parsed_uri->scheme) { + /* Copy the scheme and lower-case it */ + lcs = apr_pstrdup(p, parsed_uri->scheme); + ap_str_tolower(lcs); + /* const work-around */ + scheme = lcs; + } + else { + if (conf->base_uri && conf->base_uri->scheme) { + scheme = conf->base_uri->scheme; + } + 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 (parsed_uri->port_str) { + port_str = apr_pcalloc(p, strlen(parsed_uri->port_str) + 2); + port_str[0] = ':'; + for (i = 0; parsed_uri->port_str[i]; i++) { + port_str[i + 1] = apr_tolower(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 { + if (conf->base_uri && conf->base_uri->port_str) { + port_str = conf->base_uri->port_str; + } + else if (conf->base_uri && conf->base_uri->hostname) { + port_str = ""; + } + else { + /* Use the server port */ + port_str = apr_psprintf(p, ":%u", ap_get_server_port(r)); + } + } + + /* + * Check if we need to ignore session identifiers in the URL and do so + * if needed. + */ + kpath = path; + kquery = conf->ignorequerystring ? NULL : query; + if (conf->ignore_session_id->nelts) { + int i; + char **identifier; + + identifier = (char **) conf->ignore_session_id->elts; + for (i = 0; i < conf->ignore_session_id->nelts; i++, identifier++) { + int len; + const char *param; + + len = strlen(*identifier); + /* + * Check that we have a parameter separator in the last segment + * of the path and that the parameter matches our identifier + */ + if ((param = ap_strrchr_c(kpath, ';')) + && !strncmp(param + 1, *identifier, len) + && (*(param + len + 1) == '=') + && !ap_strchr_c(param + len + 2, '/')) { + kpath = apr_pstrmemdup(p, kpath, param - kpath); + continue; + } + /* + * Check if the identifier is in the query string and cut it out. + */ + if (kquery && *kquery) { + /* + * First check if the identifier is at the beginning of the + * query string and followed by a '=' + */ + if (!strncmp(kquery, *identifier, len) && kquery[len] == '=') { + param = kquery; + } + else { + char *complete; + + /* + * In order to avoid subkey matching (PR 48401) prepend + * identifier with a '&' and append a '=' + */ + complete = apr_pstrcat(p, "&", *identifier, "=", NULL); + param = ap_strstr_c(kquery, complete); + /* If we found something we are sitting on the '&' */ + if (param) { + param++; + } + } + if (param) { + const char *amp; + char *dup = NULL; + + if (kquery != param) { + dup = apr_pstrmemdup(p, kquery, param - kquery); + kquery = dup; + } + else { + kquery = ""; + } + + if ((amp = ap_strchr_c(param + len + 1, '&'))) { + kquery = apr_pstrcat(p, kquery, amp + 1, NULL); + } + else { + /* + * If query string is not "", then we have the case + * that the identifier parameter we removed was the + * last one in the original query string. Hence we have + * a trailing '&' which needs to be removed. + */ + if (dup) { + dup[strlen(dup) - 1] = '\0'; + } + } + } + } + } + } + + /* Key format is a URI, optionally without the query-string (NULL + * per above if conf->ignorequerystring) + */ + *key = apr_pstrcat(p, scheme, "://", hostname, port_str, + kpath, "?", kquery, NULL); + + /* + * Store the key in the request_config for the cache as r->parsed_uri + * might have changed in the time from our first visit here triggered by the + * quick handler and our possible second visit triggered by the CACHE_SAVE + * filter (e.g. r->parsed_uri got unescaped). In this case we would save the + * resource in the cache under a key where it is never found by the quick + * handler during following requests. + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00698) + "cache: Key for entity %s?%s is %s", path, query, *key); + + return APR_SUCCESS; +} + +apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, + const char **key) +{ + /* In early processing (quick-handler, forward proxy), we want the initial + * query-string from r->parsed_uri, since any change before CACHE_SAVE + * shouldn't modify the key. Otherwise we want the actual query-string. + */ + const char *path = r->uri; + const char *query = r->args; + if (cache_use_early_url(r)) { + path = r->parsed_uri.path; + query = r->parsed_uri.query; + } + return cache_canonicalise_key(r, p, path, query, &r->parsed_uri, key); +} + +/* + * Invalidate a specific URL entity in all caches + * + * All cached entities for this URL are removed, usually in + * response to a POST/PUT or DELETE. + * + * This function returns OK if at least one entity was found and + * removed, and DECLINED if no cached entities were removed. + */ +int cache_invalidate(cache_request_rec *cache, request_rec *r) +{ + cache_provider_list *list; + apr_status_t rv, status = DECLINED; + cache_handle_t *h; + apr_uri_t location_uri; + apr_uri_t content_location_uri; + + const char *location, *location_key = NULL; + const char *content_location, *content_location_key = NULL; + + if (!cache) { + /* This should never happen */ + ap_log_rerror( + APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(00697) "cache: No cache request information available for key" + " generation"); + return DECLINED; + } + + if (!cache->key) { + rv = cache_generate_key(r, r->pool, &cache->key); + if (rv != APR_SUCCESS) { + return DECLINED; + } + } + + location = apr_table_get(r->headers_out, "Location"); + if (location) { + if (apr_uri_parse(r->pool, location, &location_uri) + || cache_canonicalise_key(r, r->pool, + location_uri.path, + location_uri.query, + &location_uri, &location_key) + || !(r->parsed_uri.hostname + && location_uri.hostname + && !strcmp(r->parsed_uri.hostname, + location_uri.hostname))) { + location_key = NULL; + } + } + + content_location = apr_table_get(r->headers_out, "Content-Location"); + if (content_location) { + if (apr_uri_parse(r->pool, content_location, + &content_location_uri) + || cache_canonicalise_key(r, r->pool, + content_location_uri.path, + content_location_uri.query, + &content_location_uri, + &content_location_key) + || !(r->parsed_uri.hostname + && content_location_uri.hostname + && !strcmp(r->parsed_uri.hostname, + content_location_uri.hostname))) { + content_location_key = NULL; + } + } + + /* go through the cache types */ + h = apr_palloc(r->pool, sizeof(cache_handle_t)); + + list = cache->providers; + + while (list) { + + /* invalidate the request uri */ + rv = list->provider->open_entity(h, r, cache->key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02468) "cache: Attempted to invalidate cached entity with key: %s", cache->key); + + /* invalidate the Location */ + if (location_key) { + rv = list->provider->open_entity(h, r, location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02469) "cache: Attempted to invalidate cached entity with key: %s", location_key); + } + + /* invalidate the Content-Location */ + if (content_location_key) { + rv = list->provider->open_entity(h, r, content_location_key); + if (OK == rv) { + rv = list->provider->invalidate_entity(h, r); + status = OK; + } + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02470) "cache: Attempted to invalidate cached entity with key: %s", content_location_key); + } + + list = list->next; + } + + return status; +} diff --git a/modules/cache/cache_storage.h b/modules/cache/cache_storage.h new file mode 100644 index 0000000..83f2946 --- /dev/null +++ b/modules/cache/cache_storage.h @@ -0,0 +1,76 @@ +/* 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_storage.h + * @brief Cache Storage Functions + * + * @defgroup Cache_storage Cache Storage Functions + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_STORAGE_H +#define CACHE_STORAGE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mod_cache.h" +#include "cache_util.h" + +/** + * cache_storage.c + */ +int cache_remove_url(cache_request_rec *cache, request_rec *r); +int cache_create_entity(cache_request_rec *cache, request_rec *r, + apr_off_t size, apr_bucket_brigade *in); +int cache_select(cache_request_rec *cache, request_rec *r); + +/** + * invalidate a specific URL entity in all caches + * + * All cached entities for this URL are removed, usually in + * response to a POST/PUT or DELETE. + * + * This function returns OK if at least one entity was found and + * removed, and DECLINED if no cached entities were removed. + * @param cache cache_request_rec + * @param r request_rec + */ +int cache_invalidate(cache_request_rec *cache, request_rec *r); + +apr_status_t cache_generate_key_default(request_rec *r, apr_pool_t* p, + const char **key); + +/** + * Merge in cached headers into the response + * @param h cache_handle_t + * @param r request_rec + * @param top headers to be applied + * @param bottom headers to be overwritten + * @param revalidation true if revalidation is taking place + */ +void cache_accept_headers(cache_handle_t *h, request_rec *r, apr_table_t *top, + apr_table_t *bottom, int revalidation); + +#ifdef __cplusplus +} +#endif + +#endif /* !CACHE_STORAGE_H */ +/** @} */ diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c new file mode 100644 index 0000000..aa04913 --- /dev/null +++ b/modules/cache/cache_util.c @@ -0,0 +1,1344 @@ +/* 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 "mod_cache.h" + +#include "cache_util.h" +#include <ap_provider.h> + +APLOG_USE_MODULE(cache); + +/* -------------------------------------------------------------- */ + +extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key; + +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(const apr_uri_t *filter, const int pathlen, + const apr_uri_t *url, const char *path) +{ + /* Scheme, hostname port and local part. The filter URI and the + * URI we test may have the following shapes: + * /<path> + * <scheme>[:://<hostname>[:<port>][/<path>]] + * That is, if there is no scheme then there must be only the path, + * and we check only the path; if there is a scheme, we check the + * scheme for equality, and then if present we match the hostname, + * and then if present match the port, and finally the path if any. + * + * Note that this means that "/<path>" only matches local paths, + * and to match proxied paths one *must* specify the scheme. + */ + + /* Is the filter is just for a local path or a proxy URI? */ + if (!filter->scheme) { + if (url->scheme || url->hostname) { + return 0; + } + } + else { + /* The URI scheme must be present and identical except for case. */ + if (!url->scheme || ap_cstr_casecmp(filter->scheme, url->scheme)) { + return 0; + } + + /* If the filter hostname is null or empty it matches any hostname, + * if it begins with a "*" it matches the _end_ of the URI hostname + * excluding the "*", if it begins with a "." it matches the _end_ + * of the URI * hostname including the ".", otherwise it must match + * the URI hostname exactly. */ + + if (filter->hostname && filter->hostname[0]) { + if (filter->hostname[0] == '.') { + const size_t fhostlen = strlen(filter->hostname); + const size_t uhostlen = url->hostname ? strlen(url->hostname) : 0; + + if (fhostlen > uhostlen + || (url->hostname + && strcasecmp(filter->hostname, + url->hostname + uhostlen - fhostlen))) { + return 0; + } + } + else if (filter->hostname[0] == '*') { + const size_t fhostlen = strlen(filter->hostname + 1); + const size_t uhostlen = url->hostname ? strlen(url->hostname) : 0; + + if (fhostlen > uhostlen + || (url->hostname + && strcasecmp(filter->hostname + 1, + url->hostname + uhostlen - fhostlen))) { + return 0; + } + } + else if (!url->hostname || strcasecmp(filter->hostname, url->hostname)) { + return 0; + } + } + + /* If the filter port is empty it matches any URL port. + * If the filter or URL port are missing, or the URL port is + * empty, they default to the port for their scheme. */ + + if (!(filter->port_str && !filter->port_str[0])) { + /* NOTE: ap_port_of_scheme will return 0 if given NULL input */ + const unsigned fport = filter->port_str ? filter->port + : apr_uri_port_of_scheme(filter->scheme); + const unsigned uport = (url->port_str && url->port_str[0]) + ? url->port : apr_uri_port_of_scheme(url->scheme); + + if (fport != uport) { + return 0; + } + } + } + + /* For HTTP caching purposes, an empty (NULL) path is equivalent to + * a single "/" path. RFCs 3986/2396 + */ + if (!path) { + if (*filter->path == '/' && pathlen == 1) { + return 1; + } + else { + return 0; + } + } + + /* Url has met all of the filter conditions so far, determine + * if the paths match. + */ + return !strncmp(filter->path, path, pathlen); +} + +int cache_use_early_url(request_rec *r) +{ + cache_server_conf *conf; + + if (r->proxyreq == PROXYREQ_PROXY) { + return 1; + } + + conf = ap_get_module_config(r->server->module_config, &cache_module); + if (conf->quick) { + return 1; + } + + return 0; +} + +static cache_provider_list *get_provider(request_rec *r, struct cache_enable *ent, + cache_provider_list *providers) +{ + /* Fetch from global config and add to the list. */ + cache_provider *provider; + provider = ap_lookup_provider(CACHE_PROVIDER_GROUP, ent->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->type; + newp->provider = provider; + + if (!providers) { + providers = newp; + } + else { + cache_provider_list *last = providers; + + while (last->next) { + if (last->provider == provider) { + return providers; + } + last = last->next; + } + if (last->provider == provider) { + return providers; + } + last->next = newp; + } + } + + return providers; +} + +cache_provider_list *cache_get_providers(request_rec *r, + cache_server_conf *conf) +{ + cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module); + cache_provider_list *providers = NULL; + const char *path; + int i; + + /* per directory cache disable */ + if (dconf->disable) { + return NULL; + } + + path = cache_use_early_url(r) ? r->parsed_uri.path : r->uri; + + /* global cache disable */ + 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, + &r->parsed_uri, path)) { + /* Stop searching now. */ + return NULL; + } + } + + /* loop through all the per directory cacheenable entries */ + for (i = 0; i < dconf->cacheenable->nelts; i++) { + struct cache_enable *ent = + (struct cache_enable *)dconf->cacheenable->elts; + providers = get_provider(r, &ent[i], providers); + } + + /* loop through all the global 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, + &r->parsed_uri, path)) { + providers = get_provider(r, &ent[i], providers); + } + } + + 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; + + if (current_age < 0) { + current_age = 0; + } + + return apr_time_sec(current_age); +} + +/** + * Try obtain a cache wide lock on the given cache key. + * + * If we return APR_SUCCESS, we obtained the lock, and we are clear to + * proceed to the backend. If we return APR_EEXIST, then the lock is + * already locked, someone else has gone to refresh the backend data + * already, so we must return stale data with a warning in the mean + * time. If we return anything else, then something has gone pear + * shaped, and we allow the request through to the backend regardless. + * + * This lock is created from the request pool, meaning that should + * something go wrong and the lock isn't deleted on return of the + * request headers from the backend for whatever reason, at worst the + * lock will be cleaned up when the request dies or finishes. + * + * If something goes truly bananas and the lock isn't deleted when the + * request dies, the lock will be trashed when its max-age is reached, + * or when a request arrives containing a Cache-Control: no-cache. At + * no point is it possible for this lock to permanently deny access to + * the backend. + */ +apr_status_t cache_try_lock(cache_server_conf *conf, cache_request_rec *cache, + request_rec *r) +{ + apr_status_t status; + const char *lockname; + const char *path; + char dir[5]; + apr_time_t now = apr_time_now(); + apr_finfo_t finfo; + apr_file_t *lockfile; + void *dummy; + + finfo.mtime = 0; + + if (!conf || !conf->lock || !conf->lockpath) { + /* no locks configured, leave */ + return APR_SUCCESS; + } + + /* lock already obtained earlier? if so, success */ + apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); + if (dummy) { + return APR_SUCCESS; + } + + /* create the key if it doesn't exist */ + if (!cache->key) { + cache_handle_t *h; + /* + * Try to use the key of a possible open but stale cache + * entry if we have one. + */ + if (cache->handle != NULL) { + h = cache->handle; + } + else { + h = cache->stale_handle; + } + if ((h != NULL) && + (h->cache_obj != NULL) && + (h->cache_obj->key != NULL)) { + cache->key = apr_pstrdup(r->pool, h->cache_obj->key); + } + else { + cache_generate_key(r, r->pool, &cache->key); + } + } + + /* create a hashed filename from the key, and save it for later */ + lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key); + + /* lock files represent discrete just-went-stale URLs "in flight", so + * we support a simple two level directory structure, more is overkill. + */ + dir[0] = '/'; + dir[1] = lockname[0]; + dir[2] = '/'; + dir[3] = lockname[1]; + dir[4] = 0; + + /* make the directories */ + path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL); + if (APR_SUCCESS != (status = apr_dir_make_recursive(path, + APR_UREAD|APR_UWRITE|APR_UEXECUTE, r->pool))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00778) + "Could not create a cache lock directory: %s", + path); + return status; + } + lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL); + apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool); + + /* is an existing lock file too old? */ + status = apr_stat(&finfo, lockname, + APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool); + if (!(APR_STATUS_IS_ENOENT(status)) && APR_SUCCESS != status) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00779) + "Could not stat a cache lock file: %s", + lockname); + return status; + } + if ((status == APR_SUCCESS) && (((now - finfo.mtime) > conf->lockmaxage) + || (now < finfo.mtime))) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO(00780) + "Cache lock file for '%s' too old, removing: %s", + r->uri, lockname); + apr_file_remove(lockname, r->pool); + } + + /* try obtain a lock on the file */ + if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname, + APR_WRITE | APR_CREATE | APR_EXCL | APR_DELONCLOSE, + APR_UREAD | APR_UWRITE, r->pool))) { + apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool); + } + return status; + +} + +/** + * Remove the cache lock, if present. + * + * First, try to close the file handle, whose delete-on-close should + * kill the file. Otherwise, just delete the file by name. + * + * If no lock name has yet been calculated, do the calculation of the + * lock name first before trying to delete the file. + * + * If an optional bucket brigade is passed, the lock will only be + * removed if the bucket brigade contains an EOS bucket. + */ +apr_status_t cache_remove_lock(cache_server_conf *conf, + cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb) +{ + void *dummy; + const char *lockname; + + if (!conf || !conf->lock || !conf->lockpath) { + /* no locks configured, leave */ + return APR_SUCCESS; + } + if (bb) { + apr_bucket *e; + int eos_found = 0; + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { + if (APR_BUCKET_IS_EOS(e)) { + eos_found = 1; + break; + } + } + if (!eos_found) { + /* no eos found in brigade, don't delete anything just yet, + * we are not done. + */ + return APR_SUCCESS; + } + } + apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); + if (dummy) { + return apr_file_close((apr_file_t *)dummy); + } + apr_pool_userdata_get(&dummy, CACHE_LOCKNAME_KEY, r->pool); + lockname = (const char *)dummy; + if (!lockname) { + char dir[5]; + + /* create the key if it doesn't exist */ + if (!cache->key) { + cache_generate_key(r, r->pool, &cache->key); + } + + /* create a hashed filename from the key, and save it for later */ + lockname = ap_cache_generate_name(r->pool, 0, 0, cache->key); + + /* lock files represent discrete just-went-stale URLs "in flight", so + * we support a simple two level directory structure, more is overkill. + */ + dir[0] = '/'; + dir[1] = lockname[0]; + dir[2] = '/'; + dir[3] = lockname[1]; + dir[4] = 0; + + lockname = apr_pstrcat(r->pool, conf->lockpath, dir, "/", lockname, NULL); + } + return apr_file_remove(lockname, r->pool); +} + +int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r) +{ + + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * At this point, we may have data cached, but the request may have + * specified that cached data may not be used in a response. + * + * This is covered under RFC2616 section 14.9.4 (Cache Revalidation and + * Reload Controls). + * + * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache, or Pragma: + * no-cache. The server MUST NOT use a cached copy when responding to such + * a request. + */ + + /* This value comes from the client's initial request. */ + if (!cache->control_in.parsed) { + const char *cc_req = cache_table_getm(r->pool, r->headers_in, + "Cache-Control"); + const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma"); + ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + } + + if (cache->control_in.no_cache) { + + if (!conf->ignorecachecontrol) { + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02657) + "Incoming request is asking for an uncached version of " + "%s, but we have been configured to ignore it and serve " + "cached content anyway", r->unparsed_uri); + } + } + + return 1; +} + +int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r) +{ + + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(r->server->module_config, + &cache_module); + + /* + * At this point, we may have data cached, but the request may have + * specified that cached data may not be used in a response. + * + * - RFC2616 14.9.2 What May be Stored by Caches. If Cache-Control: + * no-store arrives, do not serve from or store to the cache. + */ + + /* This value comes from the client's initial request. */ + if (!cache->control_in.parsed) { + const char *cc_req = cache_table_getm(r->pool, r->headers_in, + "Cache-Control"); + const char *pragma = cache_table_getm(r->pool, r->headers_in, "Pragma"); + ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + } + + if (cache->control_in.no_store) { + + if (!conf->ignorecachecontrol) { + /* We're not allowed to serve a cached copy */ + return 0; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02658) + "Incoming request is asking for a no-store version of " + "%s, but we have been configured to ignore it and serve " + "cached content anyway", r->unparsed_uri); + } + } + + return 1; +} + +int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, + request_rec *r) +{ + apr_status_t status; + apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale; + apr_int64_t minfresh; + const char *cc_req; + const char *pragma; + const char *agestr = NULL; + apr_time_t age_c = 0; + cache_info *info = &(h->cache_obj->info); + const char *warn_head; + 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 + * perform the request unconditionally, and ignore cached content. We + * should never reach here, but if we do, mark the content as stale, + * as this is the best we can do. + * + * - 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"); + + ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); + + if (cache->control_in.no_cache) { + + if (!conf->ignorecachecontrol) { + /* Treat as stale, causing revalidation */ + return 0; + } + + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00781) + "Incoming request is asking for a uncached version of " + "%s, but we have been configured to ignore it and " + "serve a cached response anyway", + r->unparsed_uri); + } + + /* These come from the cached entity. */ + if (h->cache_obj->info.control.no_cache + || h->cache_obj->info.control.invalidated) { + /* + * The cached entity contained Cache-Control: no-cache, or a + * no-cache with a header present, or a private with a header + * present, or the cached entity has been invalidated in the + * past, so treat as stale causing revalidation. + */ + return 0; + } + + if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) { + char *endp; + apr_off_t offt; + if (!apr_strtoff(&offt, agestr, &endp, 10) + && endp > agestr && !*endp) { + age_c = offt; + } + } + + /* calculate age of object */ + age = ap_cache_current_age(info, age_c, r->request_time); + + /* extract s-maxage */ + smaxage = h->cache_obj->info.control.s_maxage_value; + + /* extract max-age from request */ + maxage_req = -1; + if (!conf->ignorecachecontrol) { + maxage_req = cache->control_in.max_age_value; + } + + /* + * extract max-age from response, if both s-maxage and max-age, s-maxage + * takes priority + */ + if (smaxage != -1) { + maxage_cresp = smaxage; + } + else { + maxage_cresp = h->cache_obj->info.control.max_age_value; + } + + /* + * 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 (cache->control_in.max_stale) { + if (cache->control_in.max_stale_value != -1) { + maxstale = cache->control_in.max_stale_value; + } + else { + /* + * If no value is assigned to max-stale, then the client is willing + * to accept a stale response of any age (RFC2616 14.9.3). We will + * set it to one year in this case as this situation is somewhat + * similar to a "never expires" Expires header (RFC2616 14.21) + * which is set to a date one year from the time the response is + * sent in this case. + */ + maxstale = APR_INT64_C(86400*365); + } + } + else { + maxstale = 0; + } + + /* extract min-fresh */ + if (!conf->ignorecachecontrol && cache->control_in.min_fresh) { + minfresh = cache->control_in.min_fresh_value; + } + else { + minfresh = 0; + } + + /* override maxstale if must-revalidate, proxy-revalidate or s-maxage */ + if (maxstale && (h->cache_obj->info.control.must_revalidate + || h->cache_obj->info.control.proxy_revalidate || smaxage != -1)) { + maxstale = 0; + } + + /* handle expiration */ + if (((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)))) { + + 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 (!(((maxage != -1) && age < maxage) || + (info->expire != APR_DATE_BAD && + (apr_time_sec(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_mergen(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 response header age + * calculated is more than 24 hours add the warning 113 + */ + if ((maxage_cresp == -1) && (smaxage == -1) && (apr_table_get( + h->resp_hdrs, "Expires") == 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_mergen(h->resp_hdrs, "Warning", + "113 Heuristic expiration"); + } + } + return 1; /* Cache object is fresh (enough) */ + } + + /* + * At this point we are stale, but: if we are under load, we may let + * a significant number of stale requests through before the first + * stale request successfully revalidates itself, causing a sudden + * unexpected thundering herd which in turn brings angst and drama. + * + * So. + * + * We want the first stale request to go through as normal. But the + * second and subsequent request, we must pretend to be fresh until + * the first request comes back with either new content or confirmation + * that the stale content is still fresh. + * + * To achieve this, we create a very simple file based lock based on + * the key of the cached object. We attempt to open the lock file with + * exclusive write access. If we succeed, woohoo! we're first, and we + * follow the stale path to the backend server. If we fail, oh well, + * we follow the fresh path, and avoid being a thundering herd. + * + * The lock lives only as long as the stale request that went on ahead. + * If the request succeeds, the lock is deleted. If the request fails, + * the lock is deleted, and another request gets to make a new lock + * and try again. + * + * At any time, a request marked "no-cache" will force a refresh, + * ignoring the lock, ensuring an extended lockout is impossible. + * + * A lock that exceeds a maximum age will be deleted, and another + * request gets to make a new lock and try again. + */ + status = cache_try_lock(conf, cache, r); + if (APR_SUCCESS == status) { + /* we obtained a lock, follow the stale path */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00782) + "Cache lock obtained for stale cached URL, " + "revalidating entry: %s", + r->unparsed_uri); + return 0; + } + else if (APR_STATUS_IS_EEXIST(status)) { + /* lock already exists, return stale data anyway, with a warning */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00783) + "Cache already locked for stale cached URL, " + "pretend it is fresh: %s", + r->unparsed_uri); + + /* make sure we don't stomp on a previous warning */ + warn_head = apr_table_get(h->resp_hdrs, "Warning"); + if ((warn_head == NULL) || + ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) { + apr_table_mergen(h->resp_hdrs, "Warning", + "110 Response is stale"); + } + + return 1; + } + else { + /* some other error occurred, just treat the object as stale */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00784) + "Attempt to obtain a cache lock for stale " + "cached URL failed, revalidating entry anyway: %s", + r->unparsed_uri); + return 0; + } + +} + +/* 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_pstrmemdup(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); +} + +/** + * String tokenizer that ignores separator characters within quoted strings + * and escaped characters, as per RFC2616 section 2.2. + */ +char *cache_strqtok(char *str, const char *sep, char **last) +{ + char *token; + int quoted = 0; + + if (!str) { /* subsequent call */ + str = *last; /* start where we left off */ + } + + if (!str) { /* no more tokens */ + return NULL; + } + + /* skip characters in sep (will terminate at '\0') */ + while (*str && ap_strchr_c(sep, *str)) { + ++str; + } + + if (!*str) { /* no more tokens */ + return NULL; + } + + token = str; + + /* skip valid token characters to terminate token and + * prepare for the next call (will terminate at '\0) + * on the way, ignore all quoted strings, and within + * quoted strings, escaped characters. + */ + *last = token; + while (**last) { + if (!quoted) { + if (**last == '\"' && !ap_strchr_c(sep, '\"')) { + quoted = 1; + ++*last; + } + else if (!ap_strchr_c(sep, **last)) { + ++*last; + } + else { + break; + } + } + else { + if (**last == '\"') { + quoted = 0; + ++*last; + } + else if (**last == '\\') { + ++*last; + if (**last) { + ++*last; + } + } + else { + ++*last; + } + } + } + + if (**last) { + **last = '\0'; + ++*last; + } + + return token; +} + +/** + * Parse the Cache-Control and Pragma headers in one go, marking + * which tokens appear within the header. Populate the structure + * passed in. + */ +int ap_cache_control(request_rec *r, cache_control_t *cc, + const char *cc_header, const char *pragma_header, apr_table_t *headers) +{ + char *last; + + if (cc->parsed) { + return cc->cache_control || cc->pragma; + } + + cc->parsed = 1; + cc->max_age_value = -1; + cc->max_stale_value = -1; + cc->min_fresh_value = -1; + cc->s_maxage_value = -1; + + if (pragma_header) { + char *header = apr_pstrdup(r->pool, pragma_header); + const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); + while (token) { + if (!ap_cstr_casecmp(token, "no-cache")) { + cc->no_cache = 1; + } + token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); + } + cc->pragma = 1; + } + + if (cc_header) { + char *endp; + apr_off_t offt; + char *header = apr_pstrdup(r->pool, cc_header); + const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); + while (token) { + switch (token[0]) { + case 'n': + case 'N': { + if (!ap_cstr_casecmpn(token, "no-cache", 8)) { + if (token[8] == '=') { + cc->no_cache_header = 1; + } + else if (!token[8]) { + cc->no_cache = 1; + } + } + else if (!ap_cstr_casecmp(token, "no-store")) { + cc->no_store = 1; + } + else if (!ap_cstr_casecmp(token, "no-transform")) { + cc->no_transform = 1; + } + break; + } + case 'm': + case 'M': { + if (!ap_cstr_casecmpn(token, "max-age", 7)) { + if (token[7] == '=' + && !apr_strtoff(&offt, token + 8, &endp, 10) + && endp > token + 8 && !*endp) { + cc->max_age = 1; + cc->max_age_value = offt; + } + } + else if (!ap_cstr_casecmp(token, "must-revalidate")) { + cc->must_revalidate = 1; + } + else if (!ap_cstr_casecmpn(token, "max-stale", 9)) { + if (token[9] == '=' + && !apr_strtoff(&offt, token + 10, &endp, 10) + && endp > token + 10 && !*endp) { + cc->max_stale = 1; + cc->max_stale_value = offt; + } + else if (!token[9]) { + cc->max_stale = 1; + cc->max_stale_value = -1; + } + } + else if (!ap_cstr_casecmpn(token, "min-fresh", 9)) { + if (token[9] == '=' + && !apr_strtoff(&offt, token + 10, &endp, 10) + && endp > token + 10 && !*endp) { + cc->min_fresh = 1; + cc->min_fresh_value = offt; + } + } + break; + } + case 'o': + case 'O': { + if (!ap_cstr_casecmp(token, "only-if-cached")) { + cc->only_if_cached = 1; + } + break; + } + case 'p': + case 'P': { + if (!ap_cstr_casecmp(token, "public")) { + cc->public = 1; + } + else if (!ap_cstr_casecmpn(token, "private", 7)) { + if (token[7] == '=') { + cc->private_header = 1; + } + else if (!token[7]) { + cc->private = 1; + } + } + else if (!ap_cstr_casecmp(token, "proxy-revalidate")) { + cc->proxy_revalidate = 1; + } + break; + } + case 's': + case 'S': { + if (!ap_cstr_casecmpn(token, "s-maxage", 8)) { + if (token[8] == '=' + && !apr_strtoff(&offt, token + 9, &endp, 10) + && endp > token + 9 && !*endp) { + cc->s_maxage = 1; + cc->s_maxage_value = offt; + } + } + break; + } + } + token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); + } + cc->cache_control = 1; + } + + return (cc_header != NULL || pragma_header != NULL); +} + +/** + * Parse the Cache-Control, identifying and removing headers that + * exist as tokens after the no-cache and private tokens. + */ +static int cache_control_remove(request_rec *r, const char *cc_header, + apr_table_t *headers) +{ + char *last, *slast; + int found = 0; + + if (cc_header) { + char *header = apr_pstrdup(r->pool, cc_header); + char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); + while (token) { + switch (token[0]) { + case 'n': + case 'N': { + if (!ap_cstr_casecmpn(token, "no-cache", 8)) { + if (token[8] == '=') { + const char *header = cache_strqtok(token + 9, + CACHE_SEPARATOR "\"", &slast); + while (header) { + apr_table_unset(headers, header); + header = cache_strqtok(NULL, CACHE_SEPARATOR "\"", + &slast); + } + found = 1; + } + } + break; + } + case 'p': + case 'P': { + if (!ap_cstr_casecmpn(token, "private", 7)) { + if (token[7] == '=') { + const char *header = cache_strqtok(token + 8, + CACHE_SEPARATOR "\"", &slast); + while (header) { + apr_table_unset(headers, header); + header = cache_strqtok(NULL, CACHE_SEPARATOR "\"", + &slast); + } + found = 1; + } + } + break; + } + } + token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); + } + } + + return found; +} + +/* + * Create a new table consisting of those elements from an + * headers table that are allowed to be stored in a cache. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool, + apr_table_t *t, + server_rec *s) +{ + cache_server_conf *conf; + char **header; + int i; + apr_table_t *headers_out; + + /* Short circuit the common case that there are not + * (yet) any headers populated. + */ + if (t == NULL) { + return apr_table_make(pool, 10); + }; + + /* 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 + */ + 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; +} + +/* + * 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_headers_in(request_rec *r) +{ + return ap_cache_cacheable_headers(r->pool, r->headers_in, r->server); +} + +/* + * Create a new table consisting of those elements from an output + * headers table that are allowed to be stored in a cache; + * ensure there is a content type and capture any errors. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r) +{ + apr_table_t *headers_out; + + headers_out = ap_cache_cacheable_headers(r->pool, + cache_merge_headers_out(r), + r->server); + + cache_control_remove(r, + cache_table_getm(r->pool, headers_out, "Cache-Control"), + headers_out); + + return headers_out; +} + +apr_table_t *cache_merge_headers_out(request_rec *r) +{ + apr_table_t *headers_out; + + headers_out = apr_table_overlay(r->pool, r->headers_out, + r->err_headers_out); + + if (r->content_type + && !apr_table_get(headers_out, "Content-Type")) { + const char *ctype = ap_make_content_type(r, r->content_type); + if (ctype) { + apr_table_setn(headers_out, "Content-Type", ctype); + } + } + + if (r->content_encoding + && !apr_table_get(headers_out, "Content-Encoding")) { + apr_table_setn(headers_out, "Content-Encoding", + r->content_encoding); + } + + return headers_out; +} + +typedef struct +{ + apr_pool_t *p; + const char *first; + apr_array_header_t *merged; +} cache_table_getm_t; + +static int cache_table_getm_do(void *v, const char *key, const char *val) +{ + cache_table_getm_t *state = (cache_table_getm_t *) v; + + if (!state->first) { + /** + * The most common case is a single header, and this is covered by + * a fast path that doesn't allocate any memory. On the second and + * subsequent header, an array is created and the array concatenated + * together to form the final value. + */ + state->first = val; + } + else { + const char **elt; + if (!state->merged) { + state->merged = apr_array_make(state->p, 10, sizeof(const char *)); + elt = apr_array_push(state->merged); + *elt = state->first; + } + elt = apr_array_push(state->merged); + *elt = val; + } + return 1; +} + +const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t, + const char *key) +{ + cache_table_getm_t state; + + state.p = p; + state.first = NULL; + state.merged = NULL; + + apr_table_do(cache_table_getm_do, &state, t, key, NULL); + + if (!state.first) { + return NULL; + } + else if (!state.merged) { + return state.first; + } + else { + return apr_array_pstrcat(p, state.merged, ','); + } +} diff --git a/modules/cache/cache_util.h b/modules/cache/cache_util.h new file mode 100644 index 0000000..6b92151 --- /dev/null +++ b/modules/cache/cache_util.h @@ -0,0 +1,341 @@ +/* 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_util.h + * @brief Cache Storage Functions + * + * @defgroup Cache_util Cache Utility Functions + * @ingroup MOD_CACHE + * @{ + */ + +#ifndef CACHE_UTIL_H +#define CACHE_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mod_cache.h" + +#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_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_MINEXPIRE 0 +#define DEFAULT_CACHE_EXPIRE MSEC_ONE_HR +#define DEFAULT_CACHE_LMFACTOR (0.1) +#define DEFAULT_CACHE_MAXAGE 5 +#define DEFAULT_X_CACHE 0 +#define DEFAULT_X_CACHE_DETAIL 0 +#define DEFAULT_CACHE_STALE_ON_ERROR 1 +#define DEFAULT_CACHE_LOCKPATH "/mod_cache-lock" +#define CACHE_LOCKNAME_KEY "mod_cache-lockname" +#define CACHE_LOCKFILE_KEY "mod_cache-lockfile" +#define CACHE_CTX_KEY "mod_cache-ctx" +#define CACHE_SEPARATOR ", \t" + +/** + * cache_util.c + */ + +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 */ + /** store the headers that should not be stored in the cache */ + apr_array_header_t *ignore_headers; + /** store the identifiers that should not be used for key calculation */ + apr_array_header_t *ignore_session_id; + const char *lockpath; + apr_time_t lockmaxage; + apr_uri_t *base_uri; + /** ignore client's requests for uncached responses */ + unsigned int ignorecachecontrol:1; + /** ignore query-string when caching */ + unsigned int ignorequerystring:1; + /** run within the quick handler */ + unsigned int quick:1; + /* thundering herd lock */ + unsigned int lock:1; + unsigned int x_cache:1; + unsigned int x_cache_detail:1; + /* flag if CacheIgnoreHeader has been set */ + #define CACHE_IGNORE_HEADERS_SET 1 + #define CACHE_IGNORE_HEADERS_UNSET 0 + unsigned int ignore_headers_set:1; + /* flag if CacheIgnoreURLSessionIdentifiers has been set */ + #define CACHE_IGNORE_SESSION_ID_SET 1 + #define CACHE_IGNORE_SESSION_ID_UNSET 0 + unsigned int ignore_session_id_set:1; + unsigned int base_uri_set:1; + unsigned int ignorecachecontrol_set:1; + unsigned int ignorequerystring_set:1; + unsigned int quick_set:1; + unsigned int lock_set:1; + unsigned int lockpath_set:1; + unsigned int lockmaxage_set:1; + unsigned int x_cache_set:1; + unsigned int x_cache_detail_set:1; +} cache_server_conf; + +typedef struct { + /* Minimum time to keep cached files in msecs */ + apr_time_t minex; + /* Maximum time to keep cached files in msecs */ + apr_time_t maxex; + /* default time to keep cached file in msecs */ + apr_time_t defex; + /* factor for estimating expires date */ + double factor; + /* cache enabled for this location */ + apr_array_header_t *cacheenable; + /* cache disabled for this location */ + unsigned int disable:1; + /* set X-Cache headers */ + unsigned int x_cache:1; + unsigned int x_cache_detail:1; + /* serve stale on error */ + unsigned int stale_on_error:1; + /** ignore the last-modified header when deciding to cache this request */ + unsigned int no_last_mod_ignore:1; + /** ignore expiration date from server */ + unsigned int store_expired:1; + /** ignore Cache-Control: private header from server */ + unsigned int store_private:1; + /** ignore Cache-Control: no-store header from client or server */ + unsigned int store_nostore:1; + unsigned int minex_set:1; + unsigned int maxex_set:1; + unsigned int defex_set:1; + unsigned int factor_set:1; + unsigned int x_cache_set:1; + unsigned int x_cache_detail_set:1; + unsigned int stale_on_error_set:1; + unsigned int no_last_mod_ignore_set:1; + unsigned int store_expired_set:1; + unsigned int store_private_set:1; + unsigned int store_nostore_set:1; + unsigned int enable_set:1; + unsigned int disable_set:1; +} cache_dir_conf; + +/* 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 entity 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 *save_filter; /* Enable us to restore the filter on error */ + ap_filter_t *remove_url_filter; /* Enable us to remove the filter */ + const char *key; /* The cache key created for this + * request + */ + apr_off_t size; /* the content length from the headers, or -1 */ + apr_bucket_brigade *out; /* brigade to reuse for upstream responses */ + cache_control_t control_in; /* cache control incoming */ +} cache_request_rec; + +/** + * Check the whether the request allows a cached object to be served as per RFC2616 + * section 14.9.4 (Cache Revalidation and Reload Controls) + * @param cache cache_request_rec + * @param r request_rec + * @return 0 ==> cache object may not be served, 1 ==> cache object may be served + */ +int ap_cache_check_no_cache(cache_request_rec *cache, request_rec *r); + +/** + * Check the whether the request allows a cached object to be stored as per RFC2616 + * section 14.9.2 (What May be Stored by Caches) + * @param cache cache_request_rec + * @param r request_rec + * @return 0 ==> cache object may not be served, 1 ==> cache object may be served + */ +int ap_cache_check_no_store(cache_request_rec *cache, request_rec *r); + +/** + * Check the freshness of the cache object per RFC2616 section 13.2 (Expiration Model) + * @param h cache_handle_t + * @param cache cache_request_rec + * @param r request_rec + * @return 0 ==> cache object is stale, 1 ==> cache object is fresh + */ +int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, + request_rec *r); + +/** + * Try obtain a cache wide lock on the given cache key. + * + * If we return APR_SUCCESS, we obtained the lock, and we are clear to + * proceed to the backend. If we return APR_EEXISTS, then the lock is + * already locked, someone else has gone to refresh the backend data + * already, so we must return stale data with a warning in the mean + * time. If we return anything else, then something has gone pear + * shaped, and we allow the request through to the backend regardless. + * + * This lock is created from the request pool, meaning that should + * something go wrong and the lock isn't deleted on return of the + * request headers from the backend for whatever reason, at worst the + * lock will be cleaned up when the request is dies or finishes. + * + * If something goes truly bananas and the lock isn't deleted when the + * request dies, the lock will be trashed when its max-age is reached, + * or when a request arrives containing a Cache-Control: no-cache. At + * no point is it possible for this lock to permanently deny access to + * the backend. + */ +apr_status_t cache_try_lock(cache_server_conf *conf, cache_request_rec *cache, + request_rec *r); + +/** + * Remove the cache lock, if present. + * + * First, try to close the file handle, whose delete-on-close should + * kill the file. Otherwise, just delete the file by name. + * + * If no lock name has yet been calculated, do the calculation of the + * lock name first before trying to delete the file. + * + * If an optional bucket brigade is passed, the lock will only be + * removed if the bucket brigade contains an EOS bucket. + */ +apr_status_t cache_remove_lock(cache_server_conf *conf, + cache_request_rec *cache, request_rec *r, apr_bucket_brigade *bb); + +cache_provider_list *cache_get_providers(request_rec *r, + cache_server_conf *conf); + +/** + * Get a value from a table, where the table may contain multiple + * values for a given key. + * + * When the table contains a single value, that value is returned + * unchanged. + * + * When the table contains two or more values for a key, all values + * for the key are returned, separated by commas. + */ +const char *cache_table_getm(apr_pool_t *p, const apr_table_t *t, + const char *key); + +/** + * String tokenizer that ignores separator characters within quoted strings + * and escaped characters, as per RFC2616 section 2.2. + */ +char *cache_strqtok(char *str, const char *sep, char **last); + +/** + * Merge err_headers_out into headers_out and add request's Content-Type and + * Content-Encoding if available. + */ +apr_table_t *cache_merge_headers_out(request_rec *r); + +/** + * Return whether to use request's path/query from early stage (r->parsed_uri) + * or the current/rewritable ones (r->uri/r->args). + */ +int cache_use_early_url(request_rec *r); + +#ifdef __cplusplus +} +#endif + +#endif /* !CACHE_UTIL_H */ +/** @} */ diff --git a/modules/cache/config.m4 b/modules/cache/config.m4 new file mode 100644 index 0000000..8115094 --- /dev/null +++ b/modules/cache/config.m4 @@ -0,0 +1,142 @@ +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, , , most) + +dnl # list of object files for mod_cache +cache_objs="dnl +mod_cache.lo dnl +cache_storage.lo dnl +cache_util.lo dnl +" +cache_disk_objs="mod_cache_disk.lo" +cache_socache_objs="mod_cache_socache.lo" + +case "$host" in + *os2*) + # OS/2 DLLs must resolve all symbols at build time + # and we need some from main cache module + cache_disk_objs="$cache_disk_objs mod_cache.la" + cache_socache_objs="$cache_socache_objs mod_cache.la" + ;; +esac + +APACHE_MODULE(cache, dynamic file caching. At least one storage management module (e.g. mod_cache_disk) is also necessary., $cache_objs, , most) +APACHE_MODULE(cache_disk, disk caching module, $cache_disk_objs, , most, , cache) +APACHE_MODULE(cache_socache, shared object caching module, $cache_socache_objs, , most) + +dnl +dnl APACHE_CHECK_DISTCACHE +dnl +dnl Configure for the detected distcache installation, giving +dnl preference to "--with-distcache=<path>" if it was specified. +dnl +AC_DEFUN([APACHE_CHECK_DISTCACHE],[ +if test "x$ap_distcache_configured" = "x"; then + dnl initialise the variables we use + ap_distcache_found="" + ap_distcache_base="" + ap_distcache_libs="" + ap_distcache_ldflags="" + ap_distcache_with="" + + dnl Determine the distcache base directory, if any + AC_MSG_CHECKING([for user-provided distcache base]) + AC_ARG_WITH(distcache, APACHE_HELP_STRING(--with-distcache=PATH, Distcache installation directory), [ + dnl If --with-distcache specifies a directory, we use that directory or fail + if test "x$withval" != "xyes" -a "x$withval" != "x"; then + dnl This ensures $withval is actually a directory and that it is absolute + ap_distcache_with="yes" + ap_distcache_base="`cd $withval ; pwd`" + fi + ]) + if test "x$ap_distcache_base" = "x"; then + AC_MSG_RESULT(none) + else + AC_MSG_RESULT($ap_distcache_base) + fi + + dnl Run header and version checks + saved_CPPFLAGS="$CPPFLAGS" + saved_LIBS="$LIBS" + saved_LDFLAGS="$LDFLAGS" + + if test "x$ap_distcache_base" != "x"; then + APR_ADDTO(CPPFLAGS, [-I$ap_distcache_base/include]) + APR_ADDTO(MOD_INCLUDES, [-I$ap_distcache_base/include]) + APR_ADDTO(LDFLAGS, [-L$ap_distcache_base/lib]) + APR_ADDTO(ap_distcache_ldflags, [-L$ap_distcache_base/lib]) + if test "x$ap_platform_runtime_link_flag" != "x"; then + APR_ADDTO(LDFLAGS, [$ap_platform_runtime_link_flag$ap_distcache_base/lib]) + APR_ADDTO(ap_distcache_ldflags, [$ap_platform_runtime_link_flag$ap_distcache_base/lib]) + fi + fi + dnl First check for mandatory headers + AC_CHECK_HEADERS([distcache/dc_client.h], [ap_distcache_found="yes"], []) + if test "$ap_distcache_found" = "yes"; then + dnl test for a good version + AC_MSG_CHECKING(for distcache version) + AC_TRY_COMPILE([#include <distcache/dc_client.h>],[ +#if DISTCACHE_CLIENT_API != 0x0001 +#error "distcache API version is unrecognised" +#endif], + [], + [ap_distcache_found="no"]) + AC_MSG_RESULT($ap_distcache_found) + fi + if test "$ap_distcache_found" != "yes"; then + if test "x$ap_distcache_with" = "x"; then + AC_MSG_WARN([...No distcache detected]) + else + AC_MSG_ERROR([...No distcache detected]) + fi + else + dnl Run library and function checks + AC_MSG_CHECKING(for distcache libraries) + ap_distcache_libs="-ldistcache -lnal" + APR_ADDTO(LIBS, [$ap_distcache_libs]) + + AC_TRY_LINK( + [#include <distcache/dc_client.h>], + [DC_CTX *foo = DC_CTX_new((const char *)0,0);], + [], + [ap_distcache_found="no"]) + AC_MSG_RESULT($ap_distcache_found) + if test "$ap_distcache_found" != "yes"; then + if test "x$ap_distcache_base" = "x"; then + AC_MSG_WARN([... Error, distcache libraries were missing or unusable]) + else + AC_MSG_ERROR([... Error, distcache libraries were missing or unusable]) + fi + fi + fi + + dnl restore + CPPFLAGS="$saved_CPPFLAGS" + LIBS="$saved_LIBS" + LDFLAGS="$saved_LDFLAGS" + + dnl Adjust apache's configuration based on what we found above. + if test "$ap_distcache_found" = "yes"; then + APR_ADDTO(MOD_SOCACHE_DC_LDADD, [$ap_distcache_ldflags $ap_distcache_libs]) + AC_DEFINE(HAVE_DISTCACHE, 1, [Define if distcache support is enabled]) + else + enable_socache_dc=no + fi + ap_distcache_configured="yes" +fi +]) + +APACHE_MODULE(socache_shmcb, shmcb small object cache provider, , , most) +APACHE_MODULE(socache_dbm, dbm small object cache provider, , , most) +APACHE_MODULE(socache_memcache, memcache small object cache provider, , , most) +APACHE_MODULE(socache_dc, distcache small object cache provider, , , no, [ + APACHE_CHECK_DISTCACHE +]) + +APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) + +APACHE_MODPATH_FINISH diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c new file mode 100644 index 0000000..56a09f5 --- /dev/null +++ b/modules/cache/mod_cache.c @@ -0,0 +1,2720 @@ +/* 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 "mod_cache.h" + +#include "cache_storage.h" +#include "cache_util.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_filter_handle; +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; +static ap_filter_rec_t *cache_invalidate_filter_handle; + +/** + * Entity headers' names + */ +static const char *MOD_CACHE_ENTITY_HEADERS[] = { + "Allow", + "Content-Encoding", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-MD5", + "Content-Range", + "Content-Type", + "Last-Modified", + NULL +}; + +/* + * 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. + * + * By default, the cache handler runs in the quick handler, bypassing + * virtually all server processing and offering the cache its optimal + * performance. In this mode, the cache bolts onto the front of the + * server, and behaves as a discrete RFC2616 caching proxy + * implementation. + * + * Under certain circumstances, an admin might want to run the cache as + * a normal handler instead of a quick handler, allowing the cache to + * run after the authorisation hooks, or by allowing fine control over + * the placement of the cache in the filter chain. This option comes at + * a performance penalty, and should only be used to achieve specific + * caching goals where the admin understands what they are doing. + */ + +static int cache_quick_handler(request_rec *r, int lookup) +{ + apr_status_t rv; + const char *auth; + cache_provider_list *providers; + cache_request_rec *cache; + apr_bucket_brigade *out; + apr_bucket *e; + ap_filter_t *next; + ap_filter_rec_t *cache_out_handle; + cache_server_conf *conf; + + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* only run if the quick handler is enabled */ + if (!conf->quick) { + return DECLINED; + } + + /* + * Which cache module (if any) should handle this request? + */ + if (!(providers = cache_get_providers(r, conf))) { + return DECLINED; + } + + /* make space for the per request config */ + cache = apr_pcalloc(r->pool, sizeof(cache_request_rec)); + cache->size = -1; + cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + /* save away the possible providers */ + cache->providers = providers; + + /* + * Are we allowed to serve cached info at all? + */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } + + /* 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; + } + + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02461) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); + + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02462) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + 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(cache, r); + if (rv != OK) { + if (rv == DECLINED) { + if (!lookup) { + + /* try to obtain a cache lock at this point. if we succeed, + * we are the first to try and cache this url. if we fail, + * it means someone else is already trying to cache this + * url, and we should just let the request through to the + * backend without any attempt to cache. this stops + * duplicated simultaneous attempts to cache an entity. + */ + rv = cache_try_lock(conf, cache, r); + if (APR_SUCCESS == rv) { + + /* + * 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_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00749) "Adding CACHE_SAVE_SUBREQ filter for %s", + r->uri); + cache->save_filter = ap_add_output_filter_handle( + cache_save_subreq_filter_handle, cache, r, + r->connection); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00750) "Adding CACHE_SAVE filter for %s", + r->uri); + cache->save_filter = ap_add_output_filter_handle( + cache_save_filter_handle, cache, r, + r->connection); + } + + apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00751) + "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 may be + * 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 { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, + r, APLOGNO(00752) "Cache locked for url, not caching " + "response: %s", r->uri); + /* cache_select() may have added conditional headers */ + if (cache->stale_headers) { + r->headers_in = cache->stale_headers; + } + + } + } + else { + if (cache->stale_headers) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00753) "Restoring request headers for %s", + r->uri); + + r->headers_in = cache->stale_headers; + } + } + } + else { + /* error */ + return rv; + } + return DECLINED; + } + + /* we've got a cache hit! tell everyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT, + "cache hit"); + + /* if we are a lookup, we are exiting soon one way or another; Restore + * the headers. */ + if (lookup) { + if (cache->stale_headers) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00754) + "Restoring request headers."); + r->headers_in = cache->stale_headers; + } + } + + 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) { + cache_out_handle = cache_out_subreq_filter_handle; + } + else { + cache_out_handle = cache_out_filter_handle; + } + ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection); + + /* + * Remove all filters that are before the cache_out filter. This ensures + * that we kick off the filter stack with our cache_out filter being the + * first in the chain. This make sense because we want to restore things + * in the same manner as we saved them. + * There may be filters before our cache_out filter, because + * + * 1. We call ap_set_content_type during cache_select. This causes + * Content-Type specific filters to be added. + * 2. We call the insert_filter hook. This causes filters e.g. like + * the ones set with SetOutputFilter to be added. + */ + next = r->output_filters; + while (next && (next->frec != cache_out_handle)) { + ap_remove_output_filter(next); + next = next->next; + } + + /* kick off the filter stack */ + out = apr_brigade_create(r->pool, r->connection->bucket_alloc); + e = apr_bucket_eos_create(out->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(out, e); + + return ap_pass_brigade_fchk(r, out, + "cache_quick_handler(%s): ap_pass_brigade returned", + cache->provider_name); +} + +/** + * If the two filter handles are present within the filter chain, replace + * the last instance of the first filter with the last instance of the + * second filter, and return true. If the second filter is not present at + * all, the first filter is removed, and false is returned. If neither + * filter is present, false is returned and this function does nothing. + * If a stop filter is specified, processing will stop once this filter is + * reached. + */ +static int cache_replace_filter(ap_filter_t *next, ap_filter_rec_t *from, + ap_filter_rec_t *to, ap_filter_rec_t *stop) { + ap_filter_t *ffrom = NULL, *fto = NULL; + while (next && next->frec != stop) { + if (next->frec == from) { + ffrom = next; + } + if (next->frec == to) { + fto = next; + } + next = next->next; + } + if (ffrom && fto) { + ffrom->frec = fto->frec; + ffrom->ctx = fto->ctx; + ap_remove_output_filter(fto); + return 1; + } + if (ffrom) { + ap_remove_output_filter(ffrom); + } + return 0; +} + +/** + * Find the given filter, and return it if found, or NULL otherwise. + */ +static ap_filter_t *cache_get_filter(ap_filter_t *next, ap_filter_rec_t *rec) { + while (next) { + if (next->frec == rec && next->ctx) { + break; + } + next = next->next; + } + return next; +} + +/** + * The cache handler is functionally similar to the cache_quick_hander, + * however a number of steps that are required by the quick handler are + * not required here, as the normal httpd processing has already handled + * these steps. + */ +static int cache_handler(request_rec *r) +{ + apr_status_t rv; + cache_provider_list *providers; + cache_request_rec *cache; + apr_bucket_brigade *out; + apr_bucket *e; + ap_filter_t *next; + ap_filter_rec_t *cache_out_handle; + ap_filter_rec_t *cache_save_handle; + cache_server_conf *conf; + + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* only run if the quick handler is disabled */ + if (conf->quick) { + return DECLINED; + } + + /* + * Which cache module (if any) should handle this request? + */ + if (!(providers = cache_get_providers(r, conf))) { + return DECLINED; + } + + /* make space for the per request config */ + cache = apr_pcalloc(r->pool, sizeof(cache_request_rec)); + cache->size = -1; + cache->out = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + /* save away the possible providers */ + cache->providers = providers; + + /* + * Are we allowed to serve cached info at all? + */ + if (!ap_cache_check_no_store(cache, r)) { + return DECLINED; + } + + /* Are we PUT/POST/DELETE? If so, prepare to invalidate the cached entities. + */ + switch (r->method_number) { + case M_PUT: + case M_POST: + case M_DELETE: + { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02463) + "PUT/POST/DELETE: Adding CACHE_INVALIDATE filter for %s", + r->uri); + + /* Add cache_invalidate filter to this request to force a + * cache entry to be invalidated if the response is + * ultimately successful (2xx). + */ + ap_add_output_filter_handle( + cache_invalidate_filter_handle, cache, r, + r->connection); + + return DECLINED; + } + case M_GET: { + break; + } + default : { + + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02464) "cache: Method '%s' not cacheable by mod_cache, ignoring: %s", r->method, r->uri); + + 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(cache, r); + if (rv != OK) { + if (rv == DECLINED) { + + /* try to obtain a cache lock at this point. if we succeed, + * we are the first to try and cache this url. if we fail, + * it means someone else is already trying to cache this + * url, and we should just let the request through to the + * backend without any attempt to cache. this stops + * duplicated simultaneous attempts to cache an entity. + */ + rv = cache_try_lock(conf, cache, r); + if (APR_SUCCESS == rv) { + + /* + * 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_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00756) "Adding CACHE_SAVE_SUBREQ filter for %s", + r->uri); + cache_save_handle = cache_save_subreq_filter_handle; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00757) "Adding CACHE_SAVE filter for %s", + r->uri); + cache_save_handle = cache_save_filter_handle; + } + ap_add_output_filter_handle(cache_save_handle, cache, r, + r->connection); + + /* + * Did the user indicate the precise location of the + * CACHE_SAVE filter by inserting the CACHE filter as a + * marker? + * + * If so, we get cunning and replace CACHE with the + * CACHE_SAVE filter. This has the effect of inserting + * the CACHE_SAVE filter at the precise location where + * the admin wants to cache the content. All filters that + * lie before and after the original location of the CACHE + * filter will remain in place. + */ + if (cache_replace_filter(r->output_filters, + cache_filter_handle, cache_save_handle, + ap_get_input_filter_handle("SUBREQ_CORE"))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00758) "Replacing CACHE with CACHE_SAVE " + "filter for %s", r->uri); + } + + /* save away the save filter stack */ + cache->save_filter = cache_get_filter(r->output_filters, + cache_save_filter_handle); + + apr_pool_userdata_setn(cache, CACHE_CTX_KEY, NULL, r->pool); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00759) + "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 may be + * 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 { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, + r, APLOGNO(00760) "Cache locked for url, not caching " + "response: %s", r->uri); + } + } + else { + /* error */ + return rv; + } + return DECLINED; + } + + /* we've got a cache hit! tell everyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT, + "cache hit"); + + rv = ap_meets_conditions(r); + if (rv != OK) { + return rv; + } + + /* Serve up the content */ + + /* + * Add cache_out filter to serve this request. Choose + * the correct filter by checking if we are a subrequest + * or not. + */ + if (r->main) { + cache_out_handle = cache_out_subreq_filter_handle; + } + else { + cache_out_handle = cache_out_filter_handle; + } + ap_add_output_filter_handle(cache_out_handle, cache, r, r->connection); + + /* + * Did the user indicate the precise location of the CACHE_OUT filter by + * inserting the CACHE filter as a marker? + * + * If so, we get cunning and replace CACHE with the CACHE_OUT filters. + * This has the effect of inserting the CACHE_OUT filter at the precise + * location where the admin wants to cache the content. All filters that + * lie *after* the original location of the CACHE filter will remain in + * place. + */ + if (cache_replace_filter(r->output_filters, cache_filter_handle, + cache_out_handle, ap_get_input_filter_handle("SUBREQ_CORE"))) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r, APLOGNO(00761) "Replacing CACHE with CACHE_OUT filter for %s", + r->uri); + } + + /* + * Remove all filters that are before the cache_out filter. This ensures + * that we kick off the filter stack with our cache_out filter being the + * first in the chain. This make sense because we want to restore things + * in the same manner as we saved them. + * There may be filters before our cache_out filter, because + * + * 1. We call ap_set_content_type during cache_select. This causes + * Content-Type specific filters to be added. + * 2. We call the insert_filter hook. This causes filters e.g. like + * the ones set with SetOutputFilter to be added. + */ + next = r->output_filters; + while (next && (next->frec != cache_out_handle)) { + ap_remove_output_filter(next); + next = next->next; + } + + /* kick off the filter stack */ + out = apr_brigade_create(r->pool, r->connection->bucket_alloc); + e = apr_bucket_eos_create(out->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(out, e); + return ap_pass_brigade_fchk(r, out, "cache(%s): ap_pass_brigade returned", + cache->provider_name); +} + +/* + * CACHE_OUT filter + * ---------------- + * + * Deliver cached content (headers and body) up the stack. + */ +static apr_status_t cache_out_filter(ap_filter_t *f, apr_bucket_brigade *in) +{ + request_rec *r = f->r; + cache_request_rec *cache = (cache_request_rec *)f->ctx; + + if (!cache) { + /* user likely configured CACHE_OUT manually; they should use mod_cache + * configuration to do that */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00762) + "CACHE/CACHE_OUT filter enabled while caching is disabled, ignoring"); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00763) + "cache: running CACHE_OUT filter"); + + /* clean out any previous response up to EOS, if any */ + while (!APR_BRIGADE_EMPTY(in)) { + apr_bucket *e = APR_BRIGADE_FIRST(in); + if (APR_BUCKET_IS_EOS(e)) { + apr_bucket_brigade *bb = apr_brigade_create(r->pool, + r->connection->bucket_alloc); + + /* restore content type of cached response if available */ + /* Needed especially when stale content gets served. */ + const char *ct = apr_table_get(cache->handle->resp_hdrs, "Content-Type"); + if (ct) { + ap_set_content_type(r, ct); + } + + /* restore status of cached response */ + r->status = cache->handle->cache_obj->info.status; + + /* recall_headers() was called in cache_select() */ + cache->provider->recall_body(cache->handle, r->pool, bb); + APR_BRIGADE_PREPEND(in, bb); + + /* This filter is done once it has served up its content */ + ap_remove_output_filter(f); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00764) + "cache: serving %s", r->uri); + return ap_pass_brigade(f->next, in); + + } + apr_bucket_delete(e); + } + + return APR_SUCCESS; +} + +/* + * Having jumped through all the hoops and decided to cache the + * response, call store_body() for each brigade, handling the + * case where the provider can't swallow the full brigade. In this + * case, we write the brigade we were passed out downstream, and + * loop around to try and cache some more until the in brigade is + * completely empty. As soon as the out brigade contains eos, call + * commit_entity() to finalise the cached element. + */ +static int cache_save_store(ap_filter_t *f, apr_bucket_brigade *in, + cache_server_conf *conf, cache_request_rec *cache) +{ + int rv = APR_SUCCESS; + apr_bucket *e; + + /* pass the brigade in into the cache provider, which is then + * expected to move cached buckets to the out brigade, for us + * to pass up the filter stack. repeat until in is empty, or + * we fail. + */ + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) { + + rv = cache->provider->store_body(cache->handle, f->r, in, cache->out); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(00765) + "cache: Cache provider's store_body failed for URI %s", f->r->uri); + ap_remove_output_filter(f); + + /* give someone else the chance to cache the file */ + cache_remove_lock(conf, cache, f->r, NULL); + + /* give up trying to cache, just step out the way */ + APR_BRIGADE_PREPEND(in, cache->out); + return ap_pass_brigade(f->next, in); + + } + + /* does the out brigade contain eos? if so, we're done, commit! */ + for (e = APR_BRIGADE_FIRST(cache->out); + e != APR_BRIGADE_SENTINEL(cache->out); + e = APR_BUCKET_NEXT(e)) + { + if (APR_BUCKET_IS_EOS(e)) { + rv = cache->provider->commit_entity(cache->handle, f->r); + break; + } + } + + /* conditionally remove the lock as soon as we see the eos bucket */ + cache_remove_lock(conf, cache, f->r, cache->out); + + if (APR_BRIGADE_EMPTY(cache->out)) { + if (APR_BRIGADE_EMPTY(in)) { + /* cache provider wants more data before passing the brigade + * upstream, oblige the provider by leaving to fetch more. + */ + break; + } + else { + /* oops, no data out, but not all data read in either, be + * safe and stand down to prevent a spin. + */ + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(00766) + "cache: Cache provider's store_body returned an " + "empty brigade, but didn't consume all of the " + "input brigade, standing down to prevent a spin"); + ap_remove_output_filter(f); + + /* give someone else the chance to cache the file */ + cache_remove_lock(conf, cache, f->r, NULL); + + return ap_pass_brigade(f->next, in); + } + } + + rv = ap_pass_brigade(f->next, cache->out); + } + + return rv; +} + +/** + * Sanity check for 304 Not Modified responses, as per RFC2616 Section 10.3.5. + */ +static int cache_header_cmp(apr_pool_t *pool, apr_table_t *left, + apr_table_t *right, const char *key) +{ + const char *h1, *h2; + + if ((h1 = cache_table_getm(pool, left, key)) + && (h2 = cache_table_getm(pool, right, key)) && (strcmp(h1, h2))) { + return 1; + } + return 0; +} + +/* + * 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) + * + * After the various failure cases, the cache lock is proactively removed, so + * that another request is given the opportunity to attempt to cache without + * waiting for a potentially slow client to acknowledge the failure. + */ + +static apr_status_t cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) +{ + int rv = !OK; + request_rec *r = f->r; + cache_request_rec *cache = (cache_request_rec *)f->ctx; + cache_server_conf *conf; + cache_dir_conf *dconf; + cache_control_t control; + const char *cc_out, *cl, *pragma; + const char *exps, *lastmods, *dates, *etag; + apr_time_t exp, date, lastmod, now; + apr_off_t size = -1; + cache_info *info = NULL; + const char *reason, **eh; + apr_pool_t *p; + apr_bucket *e; + apr_table_t *headers; + const char *query; + + conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* Setup cache_request_rec */ + if (!cache) { + /* user likely configured CACHE_SAVE manually; they should really use + * mod_cache configuration to do that + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00767) + "CACHE/CACHE_SAVE filter enabled while caching is disabled, ignoring"); + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); + } + + 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 cacheability check and set up the + * cached file handle? + */ + if (cache->in_checked) { + return cache_save_store(f, in, conf, cache); + } + + /* + * 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. + */ + + dconf = ap_get_module_config(r->per_dir_config, &cache_module); + + /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior: + * If a cache receives a 5xx response while attempting to revalidate an + * entry, it MAY either forward this response to the requesting client, + * or act as if the server failed to respond. In the latter case, it MAY + * return a previously received response unless the cached entry + * includes the "must-revalidate" cache-control directive (see section + * 14.9). + * + * This covers the case where an error was generated behind us, for example + * by a backend server via mod_proxy. + */ + if (dconf->stale_on_error && r->status >= HTTP_INTERNAL_SERVER_ERROR) { + + ap_remove_output_filter(cache->remove_url_filter); + + if (cache->stale_handle + && !cache->stale_handle->cache_obj->info.control.must_revalidate + && !cache->stale_handle->cache_obj->info.control.proxy_revalidate) { + const char *warn_head; + + /* morph the current save filter into the out filter, and serve from + * cache. + */ + cache->handle = cache->stale_handle; + if (r->main) { + f->frec = cache_out_subreq_filter_handle; + } + else { + f->frec = cache_out_filter_handle; + } + + r->headers_out = cache->stale_handle->resp_hdrs; + + ap_set_content_type(r, apr_table_get( + cache->stale_handle->resp_hdrs, "Content-Type")); + + /* add a revalidation warning */ + warn_head = apr_table_get(r->err_headers_out, "Warning"); + if ((warn_head == NULL) || ((warn_head != NULL) + && (ap_strstr_c(warn_head, "111") == NULL))) { + apr_table_mergen(r->err_headers_out, "Warning", + "111 Revalidation failed"); + } + + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_HIT, + apr_psprintf(r->pool, + "cache hit: %d status; stale content returned", + r->status)); + + /* give someone else the chance to cache the file */ + cache_remove_lock(conf, cache, f->r, NULL); + + /* pass brigade to our morphed out filter */ + return ap_pass_brigade(f, in); + } + } + + query = cache_use_early_url(r) ? r->parsed_uri.query : r->args; + + /* 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) { + exp = apr_date_parse_http(exps); + } + 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 = cache_table_getm(r->pool, r->err_headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->err_headers_out, "Pragma"); + headers = r->err_headers_out; + if (!cc_out && !pragma) { + cc_out = cache_table_getm(r->pool, r->headers_out, "Cache-Control"); + pragma = cache_table_getm(r->pool, r->headers_out, "Pragma"); + headers = r->headers_out; + } + + /* Have we received a 304 response without any headers at all? Fall back to + * the original headers in the original cached request. + */ + if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { + if (!cc_out && !pragma) { + cc_out = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Cache-Control"); + pragma = cache_table_getm(r->pool, cache->stale_handle->resp_hdrs, + "Pragma"); + } + + /* 304 does not contain Content-Type and mod_mime regenerates the + * Content-Type based on the r->filename. This would lead to original + * Content-Type to be lost (overwriten by whatever mod_mime generates). + * We preserves the original Content-Type here. */ + ap_set_content_type(r, apr_table_get( + cache->stale_handle->resp_hdrs, "Content-Type")); + } + + /* Parse the cache control header */ + memset(&control, 0, sizeof(cache_control_t)); + ap_cache_control(r, &control, cc_out, pragma, headers); + + /* + * 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_PARTIAL_CONTENT + && 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 allow the caching of 206, but a cache implementation might choose + * to decline to cache a 206 if it doesn't know how to. + * We include 304 Not Modified here too as this is the origin server + * telling us to serve the cached copy. + */ + if (exps != NULL || cc_out != NULL) { + /* We are also allowed to cache any response given that it has a + * valid Expires or Cache Control header. If we find a either of + * those here, we pass request through the rest of the tests. From + * the RFC: + * + * A response received with any other status code (e.g. status + * codes 302 and 307) MUST NOT be returned in a reply to a + * subsequent request unless there are cache-control directives or + * another header(s) that explicitly allow it. For example, these + * include the following: an Expires header (section 14.21); a + * "max-age", "s-maxage", "must-revalidate", "proxy-revalidate", + * "public" or "private" cache-control directive (section 14.9). + * + * FIXME: Wrong if cc_out has just an extension we don't know about + */ + } + else { + reason = apr_psprintf(p, "Response status %d", r->status); + } + } + + if (reason) { + /* noop */ + } + else if (!control.s_maxage && !control.max_age && !dconf->store_expired + && exps != NULL && exp == APR_DATE_BAD) { + /* if a broken Expires header is present, don't cache it + * Unless CC: s-maxage or max-age is present + */ + reason = apr_pstrcat(p, "Broken expires header: ", exps, NULL); + } + else if (!control.s_maxage && !control.max_age + && !dconf->store_expired && exp != APR_DATE_BAD + && exp < r->request_time) { + /* if a Expires header is in the past, don't cache it + * Unless CC: s-maxage or max-age is present + */ + reason = "Expires header already expired; not cacheable"; + } + else if (!dconf->store_expired && (control.must_revalidate + || control.proxy_revalidate) && (!control.s_maxage_value + || (!control.s_maxage && !control.max_age_value)) && lastmods + == NULL && etag == NULL) { + /* if we're already stale, but can never revalidate, don't cache it */ + reason + = "s-maxage or max-age zero and no Last-Modified or Etag; not cacheable"; + } + else if (!conf->ignorequerystring && query && exps == NULL + && !control.max_age && !control.s_maxage) { + /* if a query string is present but no explicit expiration time, + * don't cache it (RFC 2616/13.9 & 13.2.1) + */ + reason = "Query string present but no explicit expiration time"; + } + 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) && (dconf->no_last_mod_ignore == 0) && !control.max_age + && !control.s_maxage) { + /* 200 OK response from HTTP/1.0 and up without Last-Modified, + * Etag, Expires, Cache-Control:max-age, or Cache-Control:s-maxage + * 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; Expires; Cache-Control:max-age or Cache-Control:s-maxage headers"; + } + else if (!dconf->store_nostore && control.no_store) { + /* RFC2616 14.9.2 Cache-Control: no-store response + * indicating do not cache, or stop now if you are + * trying to cache it. + */ + reason = "Cache-Control: no-store present"; + } + else if (!dconf->store_private && control.private) { + /* RFC2616 14.9.1 Cache-Control: private response + * this object is marked for this user's eyes only. Behave + * as a tunnel. + */ + reason = "Cache-Control: private present"; + } + else if (apr_table_get(r->headers_in, "Authorization") + && !(control.s_maxage || control.must_revalidate + || control.proxy_revalidate || control.public)) { + /* 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_find_token(NULL, apr_table_get(r->headers_out, "Vary"), "*")) { + reason = "Vary header contains '*'"; + } + else if (apr_table_get(r->subprocess_env, "no-cache") != NULL) { + reason = "environment variable 'no-cache' is set"; + } + else if (r->no_cache) { + /* or we've been asked not to cache it above */ + reason = "r->no_cache present"; + } + else if (cache->stale_handle + && APR_DATE_BAD + != (date = apr_date_parse_http( + apr_table_get(r->headers_out, "Date"))) + && date < cache->stale_handle->cache_obj->info.date) { + + /** + * 13.12 Cache Replacement: + * + * Note: a new response that has an older Date header value than + * existing cached responses is not cacheable. + */ + reason = "updated entity is older than cached entity"; + + /* while this response is not cacheable, the previous response still is */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02474) + "cache: Removing CACHE_REMOVE_URL filter."); + ap_remove_output_filter(cache->remove_url_filter); + } + else if (r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { + apr_table_t *left = cache->stale_handle->resp_hdrs; + apr_table_t *right = r->headers_out; + const char *ehs = NULL; + + /* and lastly, contradiction checks for revalidated responses + * as per RFC2616 Section 10.3.5 + */ + if (cache_header_cmp(r->pool, left, right, "ETag")) { + ehs = "ETag"; + } + for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) { + if (cache_header_cmp(r->pool, left, right, *eh)) { + ehs = (ehs) ? apr_pstrcat(r->pool, ehs, ", ", *eh, NULL) : *eh; + } + } + if (ehs) { + reason = apr_pstrcat(r->pool, "contradiction: 304 Not Modified; " + "but ", ehs, " modified", NULL); + } + } + + /** + * Enforce RFC2616 Section 10.3.5, just in case. We caught any + * inconsistencies above. + * + * If the conditional GET used a strong cache validator (see section + * 13.3.3), the response SHOULD NOT include other entity-headers. + * Otherwise (i.e., the conditional GET used a weak validator), the + * response MUST NOT include other entity-headers; this prevents + * inconsistencies between cached entity-bodies and updated headers. + */ + if (r->status == HTTP_NOT_MODIFIED) { + for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) { + apr_table_unset(r->headers_out, *eh); + } + } + + /* Hold the phone. Some servers might allow us to cache a 2xx, but + * then make their 304 responses non cacheable. RFC2616 says this: + * + * If a 304 response indicates an entity not currently cached, then + * the cache MUST disregard the response and repeat the request + * without the conditional. + * + * A 304 response with contradictory headers is technically a + * different entity, to be safe, we remove the entity from the cache. + */ + if (reason && r->status == HTTP_NOT_MODIFIED && cache->stale_handle) { + + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02473) + "cache: %s responded with an uncacheable 304, " + "retrying the request %s. Reason: %s", + cache->key, r->unparsed_uri, reason); + + /* we've got a cache conditional miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + apr_psprintf(r->pool, + "conditional cache miss: 304 was uncacheable, entity removed: %s", + reason)); + + /* remove the cached entity immediately, we might cache it again */ + ap_remove_output_filter(cache->remove_url_filter); + cache_remove_url(cache, r); + + /* let someone else attempt to cache */ + cache_remove_lock(conf, cache, r, NULL); + + /* remove this filter from the chain */ + ap_remove_output_filter(f); + + /* retry without the conditionals */ + 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"); + + /* Currently HTTP_NOT_MODIFIED, and after the redirect, handlers won't think to set status to HTTP_OK */ + r->status = HTTP_OK; + ap_internal_redirect(r->unparsed_uri, r); + + return APR_SUCCESS; + } + + if (reason) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00768) + "cache: %s not cached for request %s. Reason: %s", + cache->key, r->unparsed_uri, reason); + + /* we've got a cache miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + reason); + + /* remove this filter from the chain */ + ap_remove_output_filter(f); + + /* remove the lock file unconditionally */ + cache_remove_lock(conf, cache, r, NULL); + + /* 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 + */ + int all_buckets_here=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)) { + continue; + } + if (e->length == (apr_size_t)-1) { + break; + } + size += e->length; + } + if (!all_buckets_here) { + size = -1; + } + } + + /* remember content length to check response size against later */ + cache->size = size; + + /* It's safe to cache the response. + * + * There are two possibilities 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; + /* + * Restore the original request headers as they may be needed + * by further output filters like the byterange filter to make + * the correct decisions. + */ + r->headers_in = cache->stale_headers; + } + } + + /* no cache handle, create a new entity */ + if (!cache->handle) { + rv = cache_create_entity(cache, r, size, in); + info = apr_pcalloc(r->pool, sizeof(cache_info)); + /* We only set info->status upon the initial creation. */ + info->status = r->status; + } + + if (rv != OK) { + /* we've got a cache miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + "cache miss: cache unwilling to store response"); + + /* Caching layer declined the opportunity to cache the response */ + ap_remove_output_filter(f); + cache_remove_lock(conf, cache, r, NULL); + return ap_pass_brigade(f->next, in); + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00769) + "cache: Caching url %s for request %s", + cache->key, r->unparsed_uri); + + /* We are actually caching this response. So it does not + * make sense to remove this entity any more. + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00770) + "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. + */ + + /* store away the previously parsed cache control headers */ + memcpy(&info->control, &control, sizeof(cache_control_t)); + + /* Read the date. Generate one if one is not supplied */ + dates = apr_table_get(r->err_headers_out, "Date"); + if (dates == NULL) { + 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 */ + /* no date header (or bad header)! */ + info->date = now; + } + 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; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, + r, APLOGNO(00771) "cache: Last modified is in the future, " + "replacing with now"); + } + + + /* CC has priority over Expires. */ + if (control.s_maxage || control.max_age) { + apr_int64_t x; + + x = control.s_maxage ? control.s_maxage_value : control.max_age_value; + x = x * MSEC_ONE_SEC; + + if (x < dconf->minex) { + x = dconf->minex; + } + if (x > dconf->maxex) { + x = dconf->maxex; + } + exp = date + x; + } + + /* if no expiry date then + * if Cache-Control: s-maxage + * expiry date = date + smaxage + * if Cache-Control: max-age + * expiry date = date + max-age + * else if lastmod + * expiry date = date + min((date - lastmod) * factor, maxexpire) + * else + * expire date = date + defaultexpire + */ + + if (exp == APR_DATE_BAD) { + if ((lastmod != APR_DATE_BAD) && (lastmod < date)) { + /* 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... + */ + apr_time_t x = (apr_time_t) ((date - lastmod) * dconf->factor); + + if (x < dconf->minex) { + x = dconf->minex; + } + if (x > dconf->maxex) { + x = dconf->maxex; + } + exp = date + x; + } + else { + exp = date + dconf->defex; + } + } + info->expire = exp; + + /* We found a stale entry which wasn't really stale. */ + if (cache->stale_handle) { + + /* 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 (note that store_headers() below already selects + * the cacheable only headers using ap_cache_cacheable_headers_out(), + * here we want to keep the original headers in r->headers_out and + * forward all of them to the client, including non-cacheable ones). + */ + r->headers_out = cache_merge_headers_out(r); + apr_table_clear(r->err_headers_out); + + /* Merge in our cached headers. However, keep any updated values. */ + /* take output, overlay on top of cached */ + cache_accept_headers(cache->handle, r, r->headers_out, + cache->handle->resp_hdrs, 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; + + /* Load in the saved status and clear the status line. */ + r->status = info->status; + r->status_line = NULL; + + /* We're just saving response headers, so we are done. Commit + * the response at this point, unless there was a previous error. + */ + if (rv == APR_SUCCESS) { + rv = cache->provider->commit_entity(cache->handle, r); + } + + 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; + + /* Strip the entity headers merged from the cached headers before + * updating the entry (see cache_accept_headers() above). + */ + for (eh = MOD_CACHE_ENTITY_HEADERS; *eh; ++eh) { + apr_table_unset(r->headers_out, *eh); + } + + bkt = apr_bucket_flush_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, bkt); + } + else { + cache->provider->recall_body(cache->handle, r->pool, bb); + + bkt = apr_bucket_eos_create(bb->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, bkt); + } + + 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_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00772) + "cache: updating headers with store_headers failed. " + "Removing cached url."); + + rv = cache->provider->remove_url(cache->stale_handle, r); + if (rv != OK) { + /* Probably a mod_cache_disk cache area has been (re)mounted + * read-only, or that there is a permissions problem. + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00773) + "cache: attempt to remove url from cache unsuccessful."); + } + + /* we've got a cache conditional hit! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, + AP_CACHE_REVALIDATE, + "conditional cache hit: entity refresh failed"); + + } + else { + + /* we've got a cache conditional hit! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, + AP_CACHE_REVALIDATE, + "conditional cache hit: entity refreshed"); + + } + + /* let someone else attempt to cache */ + cache_remove_lock(conf, cache, r, NULL); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02971) + "cache: serving %s (revalidated)", r->uri); + + return ap_pass_brigade(f->next, bb); + } + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(00774) + "cache: store_headers failed"); + + /* we've got a cache miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + "cache miss: store_headers failed"); + + ap_remove_output_filter(f); + cache_remove_lock(conf, cache, r, NULL); + return ap_pass_brigade(f->next, in); + } + + /* we've got a cache miss! tell anyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, AP_CACHE_MISS, + "cache miss: attempting entity save"); + + return cache_save_store(f, in, conf, cache); +} + +/* + * 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 apr_status_t 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_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00775) + "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); + + /* remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + +/* + * CACHE_INVALIDATE filter + * ----------------------- + * + * This filter gets added in the quick handler should a PUT, POST or DELETE + * method be detected. If the response is successful, we must invalidate any + * cached entity as per RFC2616 section 13.10. + * + * CACHE_INVALIDATE 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 + * from the chain. + * + * CACHE_INVALIDATE 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. + */ +static apr_status_t cache_invalidate_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_INVALIDATE manually; they should really + * use mod_cache configuration to do that. So: + * 1. Remove ourselves + * 2. Do nothing and bail out + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02465) + "cache: CACHE_INVALIDATE enabled unexpectedly: %s", r->uri); + } + else { + + if (r->status > 299) { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02466) + "cache: response status to '%s' method is %d (>299), not invalidating cached entity: %s", r->method, r->status, r->uri); + + } + else { + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02467) + "cache: Invalidating all cached entities in response to '%s' request for %s", + r->method, r->uri); + + cache_invalidate(cache, r); + + /* we've got a cache invalidate! tell everyone who cares */ + cache_run_cache_status(cache->handle, r, r->headers_out, + AP_CACHE_INVALIDATE, apr_psprintf(r->pool, + "cache invalidated by %s", r->method)); + + } + + } + + /* remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + +/* + * CACHE filter + * ------------ + * + * This filter can be optionally inserted into the filter chain by the admin as + * a marker representing the precise location within the filter chain where + * caching is to be performed. + * + * When the filter chain is set up in the non-quick version of the URL handler, + * the CACHE filter is replaced by the CACHE_OUT or CACHE_SAVE filter, + * effectively inserting the caching filters at the point indicated by the + * admin. The CACHE filter is then removed. + * + * This allows caching to be performed before the content is passed to the + * INCLUDES filter, or to a filter that might perform transformations unique + * to the specific request and that would otherwise be non-cacheable. + */ +static apr_status_t cache_filter(ap_filter_t *f, apr_bucket_brigade *in) +{ + + cache_server_conf + *conf = + (cache_server_conf *) ap_get_module_config(f->r->server->module_config, + &cache_module); + + /* was the quick handler enabled */ + if (conf->quick) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(00776) + "cache: CACHE filter was added in quick handler mode and " + "will be ignored: %s", f->r->unparsed_uri); + } + /* otherwise we may have been bypassed, nothing to see here */ + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(00777) + "cache: CACHE filter was added twice, or was added where " + "the cache has been bypassed and will be ignored: %s", + f->r->unparsed_uri); + } + + /* we are just a marker, so let's just remove ourselves */ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, in); +} + +/** + * If configured, add the status of the caching attempt to the subprocess + * environment, and if configured, to headers in the response. + * + * The status is saved below the broad category of the status (hit, miss, + * revalidate), as well as a single cache-status key. This can be used for + * conditional logging. + * + * The status is optionally saved to an X-Cache header, and the detail of + * why a particular cache entry was cached (or not cached) is optionally + * saved to an X-Cache-Detail header. This extra detail is useful for + * service developers who may need to know whether their Cache-Control headers + * are working correctly. + */ +static int cache_status(cache_handle_t *h, request_rec *r, + apr_table_t *headers, ap_cache_status_e status, const char *reason) +{ + cache_server_conf + *conf = + (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_module); + int x_cache = 0, x_cache_detail = 0; + + switch (status) { + case AP_CACHE_HIT: { + apr_table_setn(r->subprocess_env, AP_CACHE_HIT_ENV, reason); + break; + } + case AP_CACHE_REVALIDATE: { + apr_table_setn(r->subprocess_env, AP_CACHE_REVALIDATE_ENV, reason); + break; + } + case AP_CACHE_MISS: { + apr_table_setn(r->subprocess_env, AP_CACHE_MISS_ENV, reason); + break; + } + case AP_CACHE_INVALIDATE: { + apr_table_setn(r->subprocess_env, AP_CACHE_INVALIDATE_ENV, reason); + break; + } + } + + apr_table_setn(r->subprocess_env, AP_CACHE_STATUS_ENV, reason); + + if (dconf && dconf->x_cache_set) { + x_cache = dconf->x_cache; + } + else { + x_cache = conf->x_cache; + } + if (x_cache) { + apr_table_setn(headers, "X-Cache", apr_psprintf(r->pool, "%s from %s", + status == AP_CACHE_HIT ? "HIT" + : status == AP_CACHE_REVALIDATE ? "REVALIDATE" : status + == AP_CACHE_INVALIDATE ? "INVALIDATE" : "MISS", + r->server->server_hostname)); + } + + if (dconf && dconf->x_cache_detail_set) { + x_cache_detail = dconf->x_cache_detail; + } + else { + x_cache_detail = conf->x_cache_detail; + } + if (x_cache_detail) { + apr_table_setn(headers, "X-Cache-Detail", apr_psprintf(r->pool, + "\"%s\" from %s", reason, r->server->server_hostname)); + } + + return OK; +} + +/** + * If an error has occurred, but we have a stale cached entry, restore the + * filter stack from the save filter onwards. The canned error message will + * be discarded in the process, and replaced with the cached response. + */ +static void cache_insert_error_filter(request_rec *r) +{ + void *dummy; + cache_dir_conf *dconf; + + /* ignore everything except for 5xx errors */ + if (r->status < HTTP_INTERNAL_SERVER_ERROR) { + return; + } + + dconf = ap_get_module_config(r->per_dir_config, &cache_module); + + if (!dconf->stale_on_error) { + return; + } + + /* RFC2616 13.8 Errors or Incomplete Response Cache Behavior: + * If a cache receives a 5xx response while attempting to revalidate an + * entry, it MAY either forward this response to the requesting client, + * or act as if the server failed to respond. In the latter case, it MAY + * return a previously received response unless the cached entry + * includes the "must-revalidate" cache-control directive (see section + * 14.9). + * + * This covers the case where the error was generated by our server via + * ap_die(). + */ + apr_pool_userdata_get(&dummy, CACHE_CTX_KEY, r->pool); + if (dummy) { + cache_request_rec *cache = (cache_request_rec *) dummy; + + ap_remove_output_filter(cache->remove_url_filter); + + if (cache->stale_handle && cache->save_filter + && !cache->stale_handle->cache_obj->info.control.must_revalidate + && !cache->stale_handle->cache_obj->info.control.proxy_revalidate + && !cache->stale_handle->cache_obj->info.control.s_maxage) { + const char *warn_head; + cache_server_conf + *conf = + (cache_server_conf *) ap_get_module_config(r->server->module_config, + &cache_module); + + /* morph the current save filter into the out filter, and serve from + * cache. + */ + cache->handle = cache->stale_handle; + if (r->main) { + cache->save_filter->frec = cache_out_subreq_filter_handle; + } + else { + cache->save_filter->frec = cache_out_filter_handle; + } + + r->output_filters = cache->save_filter; + + r->err_headers_out = cache->stale_handle->resp_hdrs; + + /* add a revalidation warning */ + warn_head = apr_table_get(r->err_headers_out, "Warning"); + if ((warn_head == NULL) || ((warn_head != NULL) + && (ap_strstr_c(warn_head, "111") == NULL))) { + apr_table_mergen(r->err_headers_out, "Warning", + "111 Revalidation failed"); + } + + cache_run_cache_status( + cache->handle, + r, + r->err_headers_out, + AP_CACHE_HIT, + apr_psprintf( + r->pool, + "cache hit: %d status; stale content returned", + r->status)); + + /* give someone else the chance to cache the file */ + cache_remove_lock(conf, cache, r, NULL); + + } + } + + return; +} + +/* -------------------------------------------------------------- */ +/* Setup configurable data */ + +static void *create_dir_config(apr_pool_t *p, char *dummy) +{ + cache_dir_conf *dconf = apr_pcalloc(p, sizeof(cache_dir_conf)); + + dconf->no_last_mod_ignore = 0; + dconf->store_expired = 0; + dconf->store_private = 0; + dconf->store_nostore = 0; + + /* maximum time to cache a document */ + dconf->maxex = DEFAULT_CACHE_MAXEXPIRE; + dconf->minex = DEFAULT_CACHE_MINEXPIRE; + /* default time to cache a document */ + dconf->defex = DEFAULT_CACHE_EXPIRE; + + /* factor used to estimate Expires date from LastModified date */ + dconf->factor = DEFAULT_CACHE_LMFACTOR; + + dconf->x_cache = DEFAULT_X_CACHE; + dconf->x_cache_detail = DEFAULT_X_CACHE_DETAIL; + + dconf->stale_on_error = DEFAULT_CACHE_STALE_ON_ERROR; + + /* array of providers for this URL space */ + dconf->cacheenable = apr_array_make(p, 10, sizeof(struct cache_enable)); + + return dconf; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) { + cache_dir_conf *new = (cache_dir_conf *) apr_pcalloc(p, sizeof(cache_dir_conf)); + cache_dir_conf *add = (cache_dir_conf *) addv; + cache_dir_conf *base = (cache_dir_conf *) basev; + + new->no_last_mod_ignore = (add->no_last_mod_ignore_set == 0) ? base->no_last_mod_ignore : add->no_last_mod_ignore; + new->no_last_mod_ignore_set = add->no_last_mod_ignore_set || base->no_last_mod_ignore_set; + + new->store_expired = (add->store_expired_set == 0) ? base->store_expired : add->store_expired; + new->store_expired_set = add->store_expired_set || base->store_expired_set; + new->store_private = (add->store_private_set == 0) ? base->store_private : add->store_private; + new->store_private_set = add->store_private_set || base->store_private_set; + new->store_nostore = (add->store_nostore_set == 0) ? base->store_nostore : add->store_nostore; + new->store_nostore_set = add->store_nostore_set || base->store_nostore_set; + + /* maximum time to cache a document */ + new->maxex = (add->maxex_set == 0) ? base->maxex : add->maxex; + new->maxex_set = add->maxex_set || base->maxex_set; + new->minex = (add->minex_set == 0) ? base->minex : add->minex; + new->minex_set = add->minex_set || base->minex_set; + + /* default time to cache a document */ + new->defex = (add->defex_set == 0) ? base->defex : add->defex; + new->defex_set = add->defex_set || base->defex_set; + + /* factor used to estimate Expires date from LastModified date */ + new->factor = (add->factor_set == 0) ? base->factor : add->factor; + new->factor_set = add->factor_set || base->factor_set; + + new->x_cache = (add->x_cache_set == 0) ? base->x_cache : add->x_cache; + new->x_cache_set = add->x_cache_set || base->x_cache_set; + new->x_cache_detail = (add->x_cache_detail_set == 0) ? base->x_cache_detail + : add->x_cache_detail; + new->x_cache_detail_set = add->x_cache_detail_set + || base->x_cache_detail_set; + + new->stale_on_error = (add->stale_on_error_set == 0) ? base->stale_on_error + : add->stale_on_error; + new->stale_on_error_set = add->stale_on_error_set + || base->stale_on_error_set; + + new->cacheenable = add->enable_set ? apr_array_append(p, base->cacheenable, + add->cacheenable) : base->cacheenable; + new->enable_set = add->enable_set || base->enable_set; + new->disable = (add->disable_set == 0) ? base->disable : add->disable; + new->disable_set = add->disable_set || base->disable_set; + + return new; +} + +static void * create_cache_config(apr_pool_t *p, server_rec *s) +{ + const char *tmppath = NULL; + 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)); + ps->ignorecachecontrol = 0; + ps->ignorecachecontrol_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; + /* flag indicating that query-string should be ignored when caching */ + ps->ignorequerystring = 0; + ps->ignorequerystring_set = 0; + /* by default, run in the quick handler */ + ps->quick = 1; + ps->quick_set = 0; + /* array of identifiers that should not be used for key calculation */ + ps->ignore_session_id = apr_array_make(p, 10, sizeof(char *)); + ps->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_UNSET; + ps->lock = 0; /* thundering herd lock defaults to off */ + ps->lock_set = 0; + apr_temp_dir_get(&tmppath, p); + if (tmppath) { + ps->lockpath = apr_pstrcat(p, tmppath, DEFAULT_CACHE_LOCKPATH, NULL); + } + ps->lockmaxage = apr_time_from_sec(DEFAULT_CACHE_MAXAGE); + ps->x_cache = DEFAULT_X_CACHE; + ps->x_cache_detail = DEFAULT_X_CACHE_DETAIL; + 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); + + ps->ignorecachecontrol = + (overrides->ignorecachecontrol_set == 0) + ? base->ignorecachecontrol + : overrides->ignorecachecontrol; + ps->ignore_headers = + (overrides->ignore_headers_set == CACHE_IGNORE_HEADERS_UNSET) + ? base->ignore_headers + : overrides->ignore_headers; + ps->ignorequerystring = + (overrides->ignorequerystring_set == 0) + ? base->ignorequerystring + : overrides->ignorequerystring; + ps->ignore_session_id = + (overrides->ignore_session_id_set == CACHE_IGNORE_SESSION_ID_UNSET) + ? base->ignore_session_id + : overrides->ignore_session_id; + ps->lock = + (overrides->lock_set == 0) + ? base->lock + : overrides->lock; + ps->lockpath = + (overrides->lockpath_set == 0) + ? base->lockpath + : overrides->lockpath; + ps->lockmaxage = + (overrides->lockmaxage_set == 0) + ? base->lockmaxage + : overrides->lockmaxage; + ps->quick = + (overrides->quick_set == 0) + ? base->quick + : overrides->quick; + ps->x_cache = + (overrides->x_cache_set == 0) + ? base->x_cache + : overrides->x_cache; + ps->x_cache_detail = + (overrides->x_cache_detail_set == 0) + ? base->x_cache_detail + : overrides->x_cache_detail; + ps->base_uri = + (overrides->base_uri_set == 0) + ? base->base_uri + : overrides->base_uri; + return ps; +} + +static const char *set_cache_quick_handler(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->quick = flag; + conf->quick_set = 1; + return NULL; + +} + +static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy, + int flag) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->no_last_mod_ignore = flag; + dconf->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_expired(cmd_parms *parms, void *dummy, + int flag) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->store_expired = flag; + dconf->store_expired_set = 1; + return NULL; +} + +static const char *set_cache_store_private(cmd_parms *parms, void *dummy, + int flag) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->store_private = flag; + dconf->store_private_set = 1; + return NULL; +} + +static const char *set_cache_store_nostore(cmd_parms *parms, void *dummy, + int flag) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->store_nostore = flag; + dconf->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 (!strcasecmp(header, "None")) { + /* 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_ignore_session_id(cmd_parms *parms, void *dummy, + const char *identifier) +{ + cache_server_conf *conf; + char **new; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + if (!strcasecmp(identifier, "None")) { + /* if identifier None is listed clear array */ + conf->ignore_session_id->nelts = 0; + } + else { + if ((conf->ignore_session_id_set == CACHE_IGNORE_SESSION_ID_UNSET) || + (conf->ignore_session_id->nelts)) { + /* + * Only add identifier if no "None" has been found in identifier + * list so far. + */ + new = (char **)apr_array_push(conf->ignore_session_id); + (*new) = (char *)identifier; + } + } + conf->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_SET; + return NULL; +} + +static const char *add_cache_enable(cmd_parms *parms, void *dummy, + const char *type, + const char *url) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + cache_server_conf *conf; + struct cache_enable *new; + + const char *err = ap_check_cmd_context(parms, + NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES); + if (err != NULL) { + return err; + } + + if (*type == '/') { + return apr_psprintf(parms->pool, + "provider (%s) starts with a '/'. Are url and provider switched?", + type); + } + + if (!url) { + url = parms->path; + } + if (!url) { + return apr_psprintf(parms->pool, + "CacheEnable provider (%s) is missing an URL.", type); + } + if (parms->path && strncmp(parms->path, url, strlen(parms->path))) { + return "When in a Location, CacheEnable must specify a path or an URL below " + "that location."; + } + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + + if (parms->path) { + new = apr_array_push(dconf->cacheenable); + dconf->enable_set = 1; + } + else { + 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_dir_conf *dconf = (cache_dir_conf *)dummy; + cache_server_conf *conf; + struct cache_disable *new; + + const char *err = ap_check_cmd_context(parms, + NOT_IN_DIRECTORY|NOT_IN_LIMIT|NOT_IN_FILES); + if (err != NULL) { + return err; + } + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + + if (parms->path) { + if (!strcasecmp(url, "on")) { + dconf->disable = 1; + dconf->disable_set = 1; + return NULL; + } + else { + return "CacheDisable must be followed by the word 'on' when in a Location."; + } + } + + if (!url || (url[0] != '/' && !ap_strchr_c(url, ':'))) { + return "CacheDisable must specify a path or an URL."; + } + + 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_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->maxex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC); + dconf->maxex_set = 1; + return NULL; +} + +static const char *set_cache_minex(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->minex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC); + dconf->minex_set = 1; + return NULL; +} + +static const char *set_cache_defex(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->defex = (apr_time_t) (atol(arg) * MSEC_ONE_SEC); + dconf->defex_set = 1; + return NULL; +} + +static const char *set_cache_factor(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + double val; + + if (sscanf(arg, "%lg", &val) != 1) { + return "CacheLastModifiedFactor value must be a float"; + } + dconf->factor = val; + dconf->factor_set = 1; + return NULL; +} + +static const char *set_cache_ignore_querystring(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->ignorequerystring = flag; + conf->ignorequerystring_set = 1; + return NULL; +} + +static const char *set_cache_lock(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->lock = flag; + conf->lock_set = 1; + return NULL; +} + +static const char *set_cache_lock_path(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->lockpath = ap_server_root_relative(parms->pool, arg); + if (!conf->lockpath) { + return apr_pstrcat(parms->pool, "Invalid CacheLockPath path ", + arg, NULL); + } + conf->lockpath_set = 1; + return NULL; +} + +static const char *set_cache_lock_maxage(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + apr_int64_t seconds; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + seconds = apr_atoi64(arg); + if (seconds <= 0) { + return "CacheLockMaxAge value must be a non-zero positive integer"; + } + conf->lockmaxage = apr_time_from_sec(seconds); + conf->lockmaxage_set = 1; + return NULL; +} + +static const char *set_cache_x_cache(cmd_parms *parms, void *dummy, int flag) +{ + + if (parms->path) { + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->x_cache = flag; + dconf->x_cache_set = 1; + + } + else { + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + + conf->x_cache = flag; + conf->x_cache_set = 1; + + } + + return NULL; +} + +static const char *set_cache_x_cache_detail(cmd_parms *parms, void *dummy, int flag) +{ + + if (parms->path) { + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->x_cache_detail = flag; + dconf->x_cache_detail_set = 1; + + } + else { + cache_server_conf *conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + + conf->x_cache_detail = flag; + conf->x_cache_detail_set = 1; + + } + + return NULL; +} + +static const char *set_cache_key_base_url(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + apr_status_t rv; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->base_uri = apr_pcalloc(parms->pool, sizeof(apr_uri_t)); + rv = apr_uri_parse(parms->pool, arg, conf->base_uri); + if (rv != APR_SUCCESS) { + return apr_psprintf(parms->pool, "Could not parse '%s' as an URL.", arg); + } + else if (!conf->base_uri->scheme && !conf->base_uri->hostname && + !conf->base_uri->port_str) { + return apr_psprintf(parms->pool, "URL '%s' must contain at least one of a scheme, a hostname or a port.", arg); + } + conf->base_uri_set = 1; + return NULL; +} + +static const char *set_cache_stale_on_error(cmd_parms *parms, void *dummy, + int flag) +{ + cache_dir_conf *dconf = (cache_dir_conf *)dummy; + + dconf->stale_on_error = flag; + dconf->stale_on_error_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_TAKE12("CacheEnable", add_cache_enable, NULL, RSRC_CONF|ACCESS_CONF, + "A cache type and partial URL prefix below which " + "caching is enabled"), + AP_INIT_TAKE1("CacheDisable", add_cache_disable, NULL, RSRC_CONF|ACCESS_CONF, + "A partial URL prefix below which caching is disabled"), + AP_INIT_TAKE1("CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF|ACCESS_CONF, + "The maximum time in seconds to cache a document"), + AP_INIT_TAKE1("CacheMinExpire", set_cache_minex, NULL, RSRC_CONF|ACCESS_CONF, + "The minimum time in seconds to cache a document"), + AP_INIT_TAKE1("CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF|ACCESS_CONF, + "The default time in seconds to cache a document"), + AP_INIT_FLAG("CacheQuickHandler", set_cache_quick_handler, NULL, + RSRC_CONF, + "Run the cache in the quick handler, default on"), + AP_INIT_FLAG("CacheIgnoreNoLastMod", set_cache_ignore_no_last_mod, NULL, + RSRC_CONF|ACCESS_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("CacheStoreExpired", set_cache_store_expired, + NULL, RSRC_CONF|ACCESS_CONF, + "Ignore expiration dates when populating cache, resulting in " + "an If-Modified-Since request to the backend on retrieval"), + AP_INIT_FLAG("CacheStorePrivate", set_cache_store_private, + NULL, RSRC_CONF|ACCESS_CONF, + "Ignore 'Cache-Control: private' and store private content"), + AP_INIT_FLAG("CacheStoreNoStore", set_cache_store_nostore, + NULL, RSRC_CONF|ACCESS_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_FLAG("CacheIgnoreQueryString", set_cache_ignore_querystring, + NULL, RSRC_CONF, + "Ignore query-string when caching"), + AP_INIT_ITERATE("CacheIgnoreURLSessionIdentifiers", add_ignore_session_id, + NULL, RSRC_CONF, "A space separated list of session " + "identifiers that should be ignored for creating the key " + "of the cached entity."), + AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF|ACCESS_CONF, + "The factor used to estimate Expires date from " + "LastModified date"), + AP_INIT_FLAG("CacheLock", set_cache_lock, + NULL, RSRC_CONF, + "Enable or disable the thundering herd lock."), + AP_INIT_TAKE1("CacheLockPath", set_cache_lock_path, NULL, RSRC_CONF, + "The thundering herd lock path. Defaults to the '" + DEFAULT_CACHE_LOCKPATH "' directory in the system " + "temp directory."), + AP_INIT_TAKE1("CacheLockMaxAge", set_cache_lock_maxage, NULL, RSRC_CONF, + "Maximum age of any thundering herd lock."), + AP_INIT_FLAG("CacheHeader", set_cache_x_cache, NULL, RSRC_CONF | ACCESS_CONF, + "Add a X-Cache header to responses. Default is off."), + AP_INIT_FLAG("CacheDetailHeader", set_cache_x_cache_detail, NULL, + RSRC_CONF | ACCESS_CONF, + "Add a X-Cache-Detail header to responses. Default is off."), + AP_INIT_TAKE1("CacheKeyBaseURL", set_cache_key_base_url, NULL, RSRC_CONF, + "Override the base URL of reverse proxied cache keys."), + AP_INIT_FLAG("CacheStaleOnError", set_cache_stale_on_error, + NULL, RSRC_CONF|ACCESS_CONF, + "Serve stale content on 5xx errors if present. Defaults to on."), + {NULL} +}; + +static void register_hooks(apr_pool_t *p) +{ + /* cache initializer */ + /* cache quick handler */ + ap_hook_quick_handler(cache_quick_handler, NULL, NULL, APR_HOOK_FIRST); + /* cache handler */ + ap_hook_handler(cache_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); + /* cache status */ + cache_hook_cache_status(cache_status, NULL, NULL, APR_HOOK_MIDDLE); + /* cache error handler */ + ap_hook_insert_error_filter(cache_insert_error_filter, NULL, NULL, APR_HOOK_MIDDLE); + /* 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 is placed into the filter chain at an admin specified location, + * and when the cache_handler is run, the CACHE filter is swapped with + * the CACHE_OUT filter, or CACHE_SAVE filter as appropriate. This has + * the effect of offering optional fine control of where the cache is + * inserted into the filter chain. + */ + cache_filter_handle = + ap_register_output_filter("CACHE", + cache_filter, + NULL, + AP_FTYPE_RESOURCE); + /* + * 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 this 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 + * this 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); + cache_invalidate_filter_handle = + ap_register_output_filter("CACHE_INVALIDATE", + cache_invalidate_filter, + NULL, + AP_FTYPE_PROTOCOL); + ap_hook_post_config(cache_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); +} + +AP_DECLARE_MODULE(cache) = +{ + STANDARD20_MODULE_STUFF, + create_dir_config, /* create per-directory config structure */ + merge_dir_config, /* 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 +}; + +APR_HOOK_STRUCT( + APR_HOOK_LINK(cache_status) +) + +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(cache, CACHE, int, cache_status, + (cache_handle_t *h, request_rec *r, + apr_table_t *headers, ap_cache_status_e status, + const char *reason), (h, r, headers, status, reason), + OK, DECLINED) diff --git a/modules/cache/mod_cache.dep b/modules/cache/mod_cache.dep new file mode 100644 index 0000000..79094ac --- /dev/null +++ b/modules/cache/mod_cache.dep @@ -0,0 +1,194 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_cache.mak + +.\cache_storage.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_connection.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_main.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\http_vhost.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_date.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_md5.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apr_xlate.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_atomic.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_lib.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + ".\cache_common.h"\ + ".\cache_storage.h"\ + ".\cache_util.h"\ + ".\mod_cache.h"\ + + +.\cache_util.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_connection.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_main.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\http_vhost.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_date.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_md5.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apr_xlate.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_atomic.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_lib.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + ".\cache_common.h"\ + ".\cache_util.h"\ + ".\mod_cache.h"\ + + +.\mod_cache.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_connection.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_main.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\http_vhost.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_date.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_md5.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apr_xlate.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_atomic.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_lib.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + ".\cache_common.h"\ + ".\cache_storage.h"\ + ".\cache_util.h"\ + ".\mod_cache.h"\ + + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + diff --git a/modules/cache/mod_cache.dsp b/modules/cache/mod_cache.dsp new file mode 100644 index 0000000..40a1b34 --- /dev/null +++ b/modules/cache/mod_cache.dsp @@ -0,0 +1,131 @@ +# 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 +# Begin Special Build Tool +TargetPath=.\Release\mod_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!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 +# Begin Special Build Tool +TargetPath=.\Debug\mod_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!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_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=.\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 0000000..53e80c0 --- /dev/null +++ b/modules/cache/mod_cache.h @@ -0,0 +1,192 @@ +/* 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 + +#include "httpd.h" +#include "apr_date.h" +#include "apr_optional.h" +#include "apr_hooks.h" + +#include "cache_common.h" + +/* 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 + +/* cache info information */ +typedef struct cache_info cache_info; +struct cache_info { + /** + * the original time corresponding to the 'Date:' header of the request + * served + */ + apr_time_t date; + /** a time when the cached entity is due to expire */ + apr_time_t expire; + /** r->request_time from the same request */ + apr_time_t request_time; + /** apr_time_now() at the time the entity was actually cached */ + apr_time_t response_time; + /** + * HTTP status code of the cached entity. Though not necessarily the + * status code finally issued to the request. + */ + int status; + /* cached cache-control */ + cache_control_t control; +}; + +/* cache handle information */ +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; +}; + +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 *in, + apr_bucket_brigade *out); + 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, apr_bucket_brigade *bb); + int (*open_entity) (cache_handle_t *h, request_rec *r, + const char *urlkey); + int (*remove_url) (cache_handle_t *h, request_rec *r); + apr_status_t (*commit_entity)(cache_handle_t *h, request_rec *r); + apr_status_t (*invalidate_entity)(cache_handle_t *h, request_rec *r); +} cache_provider; + +typedef enum { + AP_CACHE_HIT, + AP_CACHE_REVALIDATE, + AP_CACHE_MISS, + AP_CACHE_INVALIDATE +} ap_cache_status_e; + +#define AP_CACHE_HIT_ENV "cache-hit" +#define AP_CACHE_REVALIDATE_ENV "cache-revalidate" +#define AP_CACHE_MISS_ENV "cache-miss" +#define AP_CACHE_INVALIDATE_ENV "cache-invalidate" +#define AP_CACHE_STATUS_ENV "cache-status" + + +/* 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); + +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(const char *)ap_cache_tokstr(apr_pool_t *p, const char *list, const char **str); + +/* Create a new table consisting of those elements from an + * headers table that are allowed to be stored in a cache. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers(apr_pool_t *pool, + apr_table_t *t, + server_rec *s); + +/* 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_headers_in(request_rec *r); + +/* Create a new table consisting of those elements from an output + * headers table that are allowed to be stored in a cache; + * ensure there is a content type and capture any errors. + */ +CACHE_DECLARE(apr_table_t *)ap_cache_cacheable_headers_out(request_rec *r); + +/** + * Parse the Cache-Control and Pragma headers in one go, marking + * which tokens appear within the header. Populate the structure + * passed in. + */ +int ap_cache_control(request_rec *r, cache_control_t *cc, const char *cc_header, + const char *pragma_header, apr_table_t *headers); + + +/* hooks */ + +/** + * Cache status hook. + * This hook is called as soon as the cache has made a decision as to whether + * an entity should be served from cache (hit), should be served from cache + * after a successful validation (revalidate), or served from the backend + * and potentially cached (miss). + * + * A basic implementation of this hook exists in mod_cache which writes this + * information to the subprocess environment, and optionally to request + * headers. Further implementations may add hooks as appropriate to perform + * more advanced processing, or to store statistics about the cache behaviour. + */ +APR_DECLARE_EXTERNAL_HOOK(cache, CACHE, int, cache_status, (cache_handle_t *h, + request_rec *r, apr_table_t *headers, ap_cache_status_e status, + const char *reason)) + +APR_DECLARE_OPTIONAL_FN(apr_status_t, + ap_cache_generate_key, + (request_rec *r, apr_pool_t*p, const char **key)); + + +#endif /*MOD_CACHE_H*/ +/** @} */ diff --git a/modules/cache/mod_cache.mak b/modules/cache/mod_cache.mak new file mode 100644 index 0000000..a89b1bc --- /dev/null +++ b/modules/cache/mod_cache.mak @@ -0,0 +1,370 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache.dsp +!IF "$(CFG)" == "" +CFG=mod_cache - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_cache - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_cache - Win32 Release" && "$(CFG)" != "mod_cache - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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 +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_cache - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\cache_storage.obj" + -@erase "$(INTDIR)\cache_util.obj" + -@erase "$(INTDIR)\mod_cache.obj" + -@erase "$(INTDIR)\mod_cache.res" + -@erase "$(INTDIR)\mod_cache_src.idb" + -@erase "$(INTDIR)\mod_cache_src.pdb" + -@erase "$(OUTDIR)\mod_cache.exp" + -@erase "$(OUTDIR)\mod_cache.lib" + -@erase "$(OUTDIR)\mod_cache.pdb" + -@erase "$(OUTDIR)\mod_cache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /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" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\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 +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache.pdb" /debug /out:"$(OUTDIR)\mod_cache.so" /implib:"$(OUTDIR)\mod_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\cache_storage.obj" \ + "$(INTDIR)\cache_util.obj" \ + "$(INTDIR)\mod_cache.obj" \ + "$(INTDIR)\mod_cache.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache.so" + if exist .\Release\mod_cache.so.manifest mt.exe -manifest .\Release\mod_cache.so.manifest -outputresource:.\Release\mod_cache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\cache_storage.obj" + -@erase "$(INTDIR)\cache_util.obj" + -@erase "$(INTDIR)\mod_cache.obj" + -@erase "$(INTDIR)\mod_cache.res" + -@erase "$(INTDIR)\mod_cache_src.idb" + -@erase "$(INTDIR)\mod_cache_src.pdb" + -@erase "$(OUTDIR)\mod_cache.exp" + -@erase "$(OUTDIR)\mod_cache.lib" + -@erase "$(OUTDIR)\mod_cache.pdb" + -@erase "$(OUTDIR)\mod_cache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "CACHE_DECLARE_EXPORT" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\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 +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache.pdb" /debug /out:"$(OUTDIR)\mod_cache.so" /implib:"$(OUTDIR)\mod_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache.so +LINK32_OBJS= \ + "$(INTDIR)\cache_storage.obj" \ + "$(INTDIR)\cache_util.obj" \ + "$(INTDIR)\mod_cache.obj" \ + "$(INTDIR)\mod_cache.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache.so" + if exist .\Debug\mod_cache.so.manifest mt.exe -manifest .\Debug\mod_cache.so.manifest -outputresource:.\Debug\mod_cache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_cache.dep") +!INCLUDE "mod_cache.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_cache.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_cache - Win32 Release" || "$(CFG)" == "mod_cache - Win32 Debug" +SOURCE=.\cache_storage.c + +"$(INTDIR)\cache_storage.obj" : $(SOURCE) "$(INTDIR)" + + +SOURCE=.\cache_util.c + +"$(INTDIR)\cache_util.obj" : $(SOURCE) "$(INTDIR)" + + +SOURCE=.\mod_cache.c + +"$(INTDIR)\mod_cache.obj" : $(SOURCE) "$(INTDIR)" + + +!IF "$(CFG)" == "mod_cache - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_cache - Win32 Release" + + +"$(INTDIR)\mod_cache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_cache - Win32 Debug" + + +"$(INTDIR)\mod_cache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache.so" /d LONG_NAME="cache_module for Apache" $(SOURCE) + + +!ENDIF + + +!ENDIF + diff --git a/modules/cache/mod_cache_disk.c b/modules/cache/mod_cache_disk.c new file mode 100644 index 0000000..52d5dba --- /dev/null +++ b/modules/cache/mod_cache_disk.c @@ -0,0 +1,1584 @@ +/* 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_lib.h" +#include "apr_file_io.h" +#include "apr_strings.h" +#include "mod_cache.h" +#include "mod_cache_disk.h" +#include "http_config.h" +#include "http_log.h" +#include "http_core.h" +#include "ap_provider.h" +#include "util_filter.h" +#include "util_script.h" +#include "util_charset.h" + +/* + * mod_cache_disk: 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 cache_disk_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 *in, + apr_bucket_brigade *out); +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 apr_status_t 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)) { + return rv; + } + *p = '/'; + ++p; + } + return APR_SUCCESS; +} + +/* 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); + + rv = mkdir_structure(conf, dest, pool); + if (rv != APR_SUCCESS) + continue; + + rv = apr_file_rename(src, dest, pool); + } + } + + return rv; +} + +static apr_status_t file_cache_el_final(disk_cache_conf *conf, disk_cache_file_t *file, + request_rec *r) +{ + apr_status_t rv = APR_SUCCESS; + + /* This assumes that the tempfiles are on the same file system + * as the cache_root. If not, then we need a file copy/move + * rather than a rename. + */ + + /* move the file over */ + if (file->tempfd) { + + rv = safe_file_rename(conf, file->tempfile, file->file, file->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00699) + "rename tempfile to file failed:" + " %s -> %s", file->tempfile, file->file); + apr_file_remove(file->tempfile, file->pool); + } + + file->tempfd = NULL; + } + + return rv; +} + +static apr_status_t file_cache_temp_cleanup(void *dummy) +{ + disk_cache_file_t *file = (disk_cache_file_t *)dummy; + + /* clean up the temporary file */ + if (file->tempfd) { + apr_file_remove(file->tempfile, file->pool); + file->tempfd = NULL; + } + file->tempfile = NULL; + file->pool = NULL; + + return APR_SUCCESS; +} + +static apr_status_t file_cache_create(disk_cache_conf *conf, disk_cache_file_t *file, + apr_pool_t *pool) +{ + file->pool = pool; + file->tempfile = apr_pstrcat(pool, conf->cache_root, AP_TEMPFILE, NULL); + + apr_pool_cleanup_register(pool, file, file_cache_temp_cleanup, apr_pool_cleanup_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; + apr_size_t len; + + /* read the data from the cache file */ + len = sizeof(disk_cache_info_t); + rv = apr_file_read_full(fd, &dobj->disk_info, len, &len); + if (rv != APR_SUCCESS) { + return rv; + } + + /* Store it away so we can get it later. */ + info->status = dobj->disk_info.status; + info->date = dobj->disk_info.date; + info->expire = dobj->disk_info.expire; + info->request_time = dobj->disk_info.request_time; + info->response_time = dobj->disk_info.response_time; + + memcpy(&info->control, &dobj->disk_info.control, sizeof(cache_control_t)); + + /* Note that we could optimize this by conditionally doing the palloc + * depending upon the size. */ + urlbuff = apr_palloc(r->pool, dobj->disk_info.name_len + 1); + len = dobj->disk_info.name_len; + rv = apr_file_read_full(fd, urlbuff, len, &len); + if (rv != APR_SUCCESS) { + return rv; + } + urlbuff[dobj->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, + apr_bucket_brigade *bb) +{ + disk_cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_disk_module); + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_disk_module); + cache_object_t *obj; + disk_cache_object_t *dobj; + apr_pool_t *pool; + + if (conf->cache_root == NULL) { + return DECLINED; + } + + /* we don't support caching of range requests (yet) */ + if (r->status == HTTP_PARTIAL_CONTENT) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00700) + "URL %s partial content response not cached", + key); + return DECLINED; + } + + /* Note, len is -1 if unknown so don't trust it too hard */ + if (len > dconf->maxfs) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00701) + "URL %s failed the size check " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->maxfs); + return DECLINED; + } + if (len >= 0 && len < dconf->minfs) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00702) + "URL %s failed the size check " + "(%" APR_OFF_T_FMT " < %" APR_OFF_T_FMT ")", + key, len, dconf->minfs); + 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_pstrmemdup(r->pool, conf->cache_root, conf->cache_root_len); + dobj->root_len = conf->cache_root_len; + + apr_pool_create(&pool, r->pool); + apr_pool_tag(pool, "mod_cache (create_entity)"); + + file_cache_create(conf, &dobj->hdrs, pool); + file_cache_create(conf, &dobj->vary, pool); + file_cache_create(conf, &dobj->data, pool); + + dobj->data.file = data_file(r->pool, conf, dobj, key); + dobj->hdrs.file = header_file(r->pool, conf, dobj, key); + dobj->vary.file = header_file(r->pool, conf, dobj, key); + + dobj->disk_info.header_only = r->header_only; + + 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, + &cache_disk_module); +#ifdef APR_SENDFILE_ENABLED + core_dir_config *coreconf = ap_get_core_module_config(r->per_dir_config); +#endif + apr_finfo_t finfo; + cache_object_t *obj; + cache_info *info; + disk_cache_object_t *dobj; + int flags; + apr_pool_t *pool; + + h->cache_obj = NULL; + + /* Look up entity keyed to 'url' */ + if (conf->cache_root == NULL) { + if (!error_logged) { + error_logged = 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00703) + "Cannot cache files to disk without a CacheRoot specified."); + } + return DECLINED; + } + + /* Create and init the cache object */ + obj = apr_pcalloc(r->pool, sizeof(cache_object_t)); + 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_pstrmemdup(r->pool, conf->cache_root, conf->cache_root_len); + dobj->root_len = conf->cache_root_len; + + dobj->vary.file = header_file(r->pool, conf, dobj, key); + flags = APR_READ|APR_BINARY|APR_BUFFERED; + rc = apr_file_open(&dobj->vary.fd, dobj->vary.file, 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->vary.fd, &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->vary.fd, &expire, len, &len); + + varray = apr_array_make(r->pool, 5, sizeof(char*)); + rc = read_array(r, varray, dobj->vary.fd); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00704) + "Cannot parse vary header file: %s", + dobj->vary.file); + apr_file_close(dobj->vary.fd); + return DECLINED; + } + apr_file_close(dobj->vary.fd); + + nkey = regen_key(r->pool, r->headers_in, varray, key); + + dobj->hashfile = NULL; + dobj->prefix = dobj->vary.file; + dobj->hdrs.file = header_file(r->pool, conf, dobj, nkey); + + flags = APR_READ|APR_BINARY|APR_BUFFERED; + rc = apr_file_open(&dobj->hdrs.fd, dobj->hdrs.file, flags, 0, r->pool); + if (rc != APR_SUCCESS) { + return DECLINED; + } + } + else if (format != DISK_FORMAT_VERSION) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00705) + "File '%s' has a version mismatch. File had version: %d.", + dobj->vary.file, format); + apr_file_close(dobj->vary.fd); + return DECLINED; + } + else { + apr_off_t offset = 0; + + /* oops, not vary as it turns out */ + dobj->hdrs.fd = dobj->vary.fd; + dobj->vary.fd = NULL; + dobj->hdrs.file = dobj->vary.file; + + /* 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->hdrs.fd, APR_SET, &offset); + nkey = key; + } + + obj->key = nkey; + dobj->key = nkey; + dobj->name = key; + + apr_pool_create(&pool, r->pool); + apr_pool_tag(pool, "mod_cache (open_entity)"); + + file_cache_create(conf, &dobj->hdrs, pool); + file_cache_create(conf, &dobj->vary, pool); + file_cache_create(conf, &dobj->data, pool); + + dobj->data.file = data_file(r->pool, conf, dobj, nkey); + + /* Read the bytes to setup the cache_info fields */ + rc = file_cache_recall_mydata(dobj->hdrs.fd, info, dobj, r); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00706) + "Cannot read header file %s", dobj->hdrs.file); + apr_file_close(dobj->hdrs.fd); + return DECLINED; + } + + + /* Is this a cached HEAD request? */ + if (dobj->disk_info.header_only && !r->header_only) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(00707) + "HEAD request cached, non-HEAD requested, ignoring: %s", + dobj->hdrs.file); + apr_file_close(dobj->hdrs.fd); + return DECLINED; + } + + /* Open the data file */ + if (dobj->disk_info.has_body) { + flags = APR_READ | APR_BINARY; +#ifdef APR_SENDFILE_ENABLED + /* When we are in the quick handler we don't have the per-directory + * configuration, so this check only takes the global setting of + * the EnableSendFile directive into account. + */ + flags |= AP_SENDFILE_ENABLED(coreconf->enable_sendfile); +#endif + rc = apr_file_open(&dobj->data.fd, dobj->data.file, flags, 0, r->pool); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(00708) + "Cannot open data file %s", dobj->data.file); + apr_file_close(dobj->hdrs.fd); + return DECLINED; + } + + rc = apr_file_info_get(&finfo, APR_FINFO_SIZE | APR_FINFO_IDENT, + dobj->data.fd); + if (rc == APR_SUCCESS) { + dobj->file_size = finfo.size; + } + + /* Atomic check - does the body file belong to the header file? */ + if (dobj->disk_info.inode == finfo.inode && + dobj->disk_info.device == finfo.device) { + + /* Initialize the cache_handle callback functions */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00709) + "Recalled cached URL info header %s", dobj->name); + + /* make the configuration stick */ + h->cache_obj = obj; + obj->vobj = dobj; + + return OK; + } + + } + else { + + /* make the configuration stick */ + h->cache_obj = obj; + obj->vobj = dobj; + + return OK; + } + + /* Oh dear, no luck matching header to the body */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00710) + "Cached URL info header '%s' didn't match body, ignoring this entry", + dobj->name); + + apr_file_close(dobj->hdrs.fd); + return DECLINED; +} + +static void close_disk_cache_fd(disk_cache_file_t *file) +{ + if (file->fd != NULL) { + apr_file_close(file->fd); + file->fd = NULL; + } + if (file->tempfd != NULL) { + apr_file_close(file->tempfd); + file->tempfd = NULL; + } +} + +static int remove_entity(cache_handle_t *h) +{ + disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj; + + close_disk_cache_fd(&(dobj->hdrs)); + close_disk_cache_fd(&(dobj->vary)); + close_disk_cache_fd(&(dobj->data)); + + /* 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, request_rec *r) +{ + 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->hdrs.file) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00711) + "Deleting %s from cache.", dobj->hdrs.file); + + rc = apr_file_remove(dobj->hdrs.file, r->pool); + 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_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(00712) + "Failed to delete headers file %s from cache.", + dobj->hdrs.file); + return DECLINED; + } + } + + /* Delete data file */ + if (dobj->data.file) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00713) + "Deleting %s from cache.", dobj->data.file); + + rc = apr_file_remove(dobj->data.file, r->pool); + 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_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(00714) + "Failed to delete data file %s from cache.", + dobj->data.file); + 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->hdrs.file ? dobj->hdrs.file : dobj->data.file; + if (str_to_copy) { + char *dir, *slash, *q; + + dir = apr_pstrdup(r->pool, 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_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00715) + "Deleting directory %s from cache", dir); + + rc = apr_dir_remove(dir, r->pool); + 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, APLOGNO(00716) + "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_full(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_full(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, rv, r, APLOGNO(00717) + "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_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00718) + "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 (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; + apr_status_t rv; + + /* This case should not happen... */ + if (!dobj->hdrs.fd) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00719) + "recalling headers; but no header fd for %s", dobj->name); + 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 */ + rv = read_table(h, r, h->resp_hdrs, dobj->hdrs.fd); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02987) + "Error reading response headers from %s for %s", + dobj->hdrs.file, dobj->name); + } + rv = read_table(h, r, h->req_hdrs, dobj->hdrs.fd); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02988) + "Error reading request headers from %s for %s", + dobj->hdrs.file, dobj->name); + } + + apr_file_close(dobj->hdrs.fd); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00720) + "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) +{ + disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj; + + if (dobj->data.fd) { + apr_brigade_insert_file(bb, dobj->data.fd, 0, dobj->file_size, p); + } + + 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_full(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_full(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_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj; + + memcpy(&h->cache_obj->info, info, sizeof(cache_info)); + + if (r->headers_out) { + dobj->headers_out = ap_cache_cacheable_headers_out(r); + } + + if (r->headers_in) { + dobj->headers_in = ap_cache_cacheable_headers_in(r); + } + + if (r->header_only && r->status != HTTP_NOT_MODIFIED) { + dobj->disk_info.header_only = 1; + } + + return APR_SUCCESS; +} + +static apr_status_t write_headers(cache_handle_t *h, request_rec *r) +{ + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_disk_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]; + + memset(&disk_info, 0, sizeof(disk_cache_info_t)); + + if (dobj->headers_out) { + const char *tmp; + + tmp = apr_table_get(dobj->headers_out, "Vary"); + + if (tmp) { + apr_array_header_t* varray; + apr_uint32_t format = VARY_FORMAT_VERSION; + + /* If we were initially opened as a vary format, rollback + * that internal state for the moment so we can recreate the + * vary format hints in the appropriate directory. + */ + if (dobj->prefix) { + dobj->hdrs.file = dobj->prefix; + dobj->prefix = NULL; + } + + rv = mkdir_structure(conf, dobj->hdrs.file, r->pool); + + rv = apr_file_mktemp(&dobj->vary.tempfd, dobj->vary.tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL, + dobj->vary.pool); + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00721) + "could not create vary file %s", + dobj->vary.tempfile); + return rv; + } + + amt = sizeof(format); + rv = apr_file_write_full(dobj->vary.tempfd, &format, amt, NULL); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00722) + "could not write to vary file %s", + dobj->vary.tempfile); + apr_file_close(dobj->vary.tempfd); + apr_pool_destroy(dobj->vary.pool); + return rv; + } + + amt = sizeof(h->cache_obj->info.expire); + rv = apr_file_write_full(dobj->vary.tempfd, + &h->cache_obj->info.expire, amt, NULL); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00723) + "could not write to vary file %s", + dobj->vary.tempfile); + apr_file_close(dobj->vary.tempfd); + apr_pool_destroy(dobj->vary.pool); + return rv; + } + + varray = apr_array_make(r->pool, 6, sizeof(char*)); + tokens_to_array(r->pool, tmp, varray); + + store_array(dobj->vary.tempfd, varray); + + rv = apr_file_close(dobj->vary.tempfd); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00724) + "could not close vary file %s", + dobj->vary.tempfile); + apr_pool_destroy(dobj->vary.pool); + return rv; + } + + tmp = regen_key(r->pool, dobj->headers_in, varray, dobj->name); + dobj->prefix = dobj->hdrs.file; + dobj->hashfile = NULL; + dobj->data.file = data_file(r->pool, conf, dobj, tmp); + dobj->hdrs.file = header_file(r->pool, conf, dobj, tmp); + } + } + + + rv = apr_file_mktemp(&dobj->hdrs.tempfd, dobj->hdrs.tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | + APR_BUFFERED | APR_EXCL, dobj->hdrs.pool); + + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00725) + "could not create header file %s", + dobj->hdrs.tempfile); + return rv; + } + + disk_info.format = DISK_FORMAT_VERSION; + disk_info.date = h->cache_obj->info.date; + disk_info.expire = h->cache_obj->info.expire; + disk_info.entity_version = dobj->disk_info.entity_version++; + disk_info.request_time = h->cache_obj->info.request_time; + disk_info.response_time = h->cache_obj->info.response_time; + disk_info.status = h->cache_obj->info.status; + disk_info.inode = dobj->disk_info.inode; + disk_info.device = dobj->disk_info.device; + disk_info.has_body = dobj->disk_info.has_body; + disk_info.header_only = dobj->disk_info.header_only; + + disk_info.name_len = strlen(dobj->name); + + memcpy(&disk_info.control, &h->cache_obj->info.control, sizeof(cache_control_t)); + + 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_full(dobj->hdrs.tempfd, (const struct iovec *) &iov, + 2, &amt); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00726) + "could not write info to header file %s", + dobj->hdrs.tempfile); + apr_file_close(dobj->hdrs.tempfd); + apr_pool_destroy(dobj->hdrs.pool); + return rv; + } + + if (dobj->headers_out) { + rv = store_table(dobj->hdrs.tempfd, dobj->headers_out); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00727) + "could not write out-headers to header file %s", + dobj->hdrs.tempfile); + apr_file_close(dobj->hdrs.tempfd); + apr_pool_destroy(dobj->hdrs.pool); + 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 (dobj->headers_in) { + rv = store_table(dobj->hdrs.tempfd, dobj->headers_in); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00728) + "could not write in-headers to header file %s", + dobj->hdrs.tempfile); + apr_file_close(dobj->hdrs.tempfd); + apr_pool_destroy(dobj->hdrs.pool); + return rv; + } + } + + rv = apr_file_close(dobj->hdrs.tempfd); /* flush and close */ + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(00729) + "could not close header file %s", + dobj->hdrs.tempfile); + apr_pool_destroy(dobj->hdrs.pool); + return rv; + } + + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, + apr_bucket_brigade *in, apr_bucket_brigade *out) +{ + apr_bucket *e; + apr_status_t rv = APR_SUCCESS; + disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj; + disk_cache_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &cache_disk_module); + int seen_eos = 0; + + if (!dobj->offset) { + dobj->offset = dconf->readsize; + } + if (!dobj->timeout && dconf->readtime) { + dobj->timeout = apr_time_now() + dconf->readtime; + } + + if (dobj->offset) { + apr_brigade_partition(in, dobj->offset, &e); + } + + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) { + const char *str; + apr_size_t length, written; + + e = APR_BRIGADE_FIRST(in); + + /* are we done completely? if so, pass any trailing buckets right through */ + if (dobj->done || !dobj->data.pool) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* have we seen eos yet? */ + if (APR_BUCKET_IS_EOS(e)) { + seen_eos = 1; + dobj->done = 1; + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* honour flush buckets, we'll get called again */ + if (APR_BUCKET_IS_FLUSH(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* metadata buckets are preserved as is */ + if (APR_BUCKET_IS_METADATA(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* read the bucket, write to the cache */ + rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00730) + "Error when reading bucket for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return rv; + } + + /* don't write empty buckets to the cache */ + if (!length) { + continue; + } + + if (!dobj->disk_info.header_only) { + + /* Attempt to create the data file at the last possible moment, if + * the body is empty, we don't write a file at all, and save an inode. + */ + if (!dobj->data.tempfd) { + apr_finfo_t finfo; + rv = apr_file_mktemp(&dobj->data.tempfd, dobj->data.tempfile, + APR_CREATE | APR_WRITE | APR_BINARY | APR_BUFFERED + | APR_EXCL, dobj->data.pool); + if (rv != APR_SUCCESS) { + apr_pool_destroy(dobj->data.pool); + return rv; + } + dobj->file_size = 0; + rv = apr_file_info_get(&finfo, APR_FINFO_IDENT, + dobj->data.tempfd); + if (rv != APR_SUCCESS) { + apr_pool_destroy(dobj->data.pool); + return rv; + } + dobj->disk_info.device = finfo.device; + dobj->disk_info.inode = finfo.inode; + dobj->disk_info.has_body = 1; + } + + /* write to the cache, leave if we fail */ + rv = apr_file_write_full(dobj->data.tempfd, str, length, &written); + if (rv != APR_SUCCESS) { + ap_log_rerror( + APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00731) "Error when writing cache file for URL %s", h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return rv; + } + dobj->file_size += written; + if (dobj->file_size > dconf->maxfs) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00732) "URL %s failed the size check " + "(%" APR_OFF_T_FMT ">%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->maxfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + + } + + /* have we reached the limit of how much we're prepared to write in one + * go? If so, leave, we'll get called again. This prevents us from trying + * to swallow too much data at once, or taking so long to write the data + * the client times out. + */ + dobj->offset -= length; + if (dobj->offset <= 0) { + dobj->offset = 0; + break; + } + if ((dconf->readtime && apr_time_now() > dobj->timeout)) { + dobj->timeout = 0; + break; + } + + } + + /* Was this the final bucket? If yes, close the temp file and perform + * sanity checks. + */ + if (seen_eos) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + + if (!dobj->disk_info.header_only) { + + if (dobj->data.tempfd) { + rv = apr_file_close(dobj->data.tempfd); + if (rv != APR_SUCCESS) { + /* Buffered write failed, abandon attempt to write */ + apr_pool_destroy(dobj->data.pool); + return rv; + } + } + + if (r->connection->aborted || r->no_cache) { + ap_log_rerror( + APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00733) "Discarding body for URL %s " + "because connection has been aborted.", h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + if (dobj->file_size < dconf->minfs) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00734) "URL %s failed the size check " + "(%" APR_OFF_T_FMT "<%" APR_OFF_T_FMT ")", h->cache_obj->key, dobj->file_size, dconf->minfs); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (dobj->file_size != cl)) { + ap_log_rerror( + APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00735) "URL %s didn't receive complete response, not caching", h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(dobj->data.pool); + return APR_EGENERAL; + } + } + + } + + /* All checks were fine, we're good to go when the commit comes */ + } + + return APR_SUCCESS; +} + +static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) +{ + disk_cache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_disk_module); + disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj; + apr_status_t rv; + + /* write the headers to disk at the last possible moment */ + rv = write_headers(h, r); + + /* move header and data tempfiles to the final destination */ + if (APR_SUCCESS == rv) { + rv = file_cache_el_final(conf, &dobj->hdrs, r); + } + if (APR_SUCCESS == rv) { + rv = file_cache_el_final(conf, &dobj->vary, r); + } + if (APR_SUCCESS == rv) { + if (!dobj->disk_info.header_only) { + rv = file_cache_el_final(conf, &dobj->data, r); + } + else if (dobj->data.file) { + rv = apr_file_remove(dobj->data.file, dobj->data.pool); + } + } + + /* remove the cached items completely on any failure */ + if (APR_SUCCESS != rv) { + remove_url(h, r); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00736) + "commit_entity: URL '%s' not cached due to earlier disk error.", + dobj->name); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00737) + "commit_entity: Headers and body for URL %s cached.", + dobj->name); + } + + apr_pool_destroy(dobj->data.pool); + + return APR_SUCCESS; +} + +static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) +{ + apr_status_t rv; + + rv = recall_headers(h, r); + if (rv != APR_SUCCESS) { + return rv; + } + + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); +} + +static void *create_dir_config(apr_pool_t *p, char *dummy) +{ + disk_cache_dir_conf *dconf = apr_pcalloc(p, sizeof(disk_cache_dir_conf)); + + dconf->maxfs = DEFAULT_MAX_FILE_SIZE; + dconf->minfs = DEFAULT_MIN_FILE_SIZE; + dconf->readsize = DEFAULT_READSIZE; + dconf->readtime = DEFAULT_READTIME; + + return dconf; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) +{ + disk_cache_dir_conf *new = (disk_cache_dir_conf *) apr_pcalloc(p, sizeof(disk_cache_dir_conf)); + disk_cache_dir_conf *add = (disk_cache_dir_conf *) addv; + disk_cache_dir_conf *base = (disk_cache_dir_conf *) basev; + + new->maxfs = (add->maxfs_set == 0) ? base->maxfs : add->maxfs; + new->maxfs_set = add->maxfs_set || base->maxfs_set; + new->minfs = (add->minfs_set == 0) ? base->minfs : add->minfs; + new->minfs_set = add->minfs_set || base->minfs_set; + new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize; + new->readsize_set = add->readsize_set || base->readsize_set; + new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime; + new->readtime_set = add->readtime_set || base->readtime_set; + + return new; +} + +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->cache_root = NULL; + conf->cache_root_len = 0; + + return conf; +} + +/* + * mod_cache_disk 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, + &cache_disk_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, + &cache_disk_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, + &cache_disk_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_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr; + + if (apr_strtoff(&dconf->minfs, arg, NULL, 10) != APR_SUCCESS || + dconf->minfs < 0) + { + return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes."; + } + dconf->minfs_set = 1; + return NULL; +} + +static const char +*set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr; + + if (apr_strtoff(&dconf->maxfs, arg, NULL, 10) != APR_SUCCESS || + dconf->maxfs < 0) + { + return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes."; + } + dconf->maxfs_set = 1; + return NULL; +} + +static const char +*set_cache_readsize(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr; + + if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS || + dconf->readsize < 0) + { + return "CacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go."; + } + dconf->readsize_set = 1; + return NULL; +} + +static const char +*set_cache_readtime(cmd_parms *parms, void *in_struct_ptr, const char *arg) +{ + disk_cache_dir_conf *dconf = (disk_cache_dir_conf *)in_struct_ptr; + apr_off_t milliseconds; + + if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS || + milliseconds < 0) + { + return "CacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go."; + } + dconf->readtime = apr_time_from_msec(milliseconds); + dconf->readtime_set = 1; + 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 | ACCESS_CONF, + "The minimum file size to cache a document"), + AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum file size to cache a document"), + AP_INIT_TAKE1("CacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum quantity of data to attempt to read and cache in one go"), + AP_INIT_TAKE1("CacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum time taken to attempt to read and cache in go"), + {NULL} +}; + +static const cache_provider cache_disk_provider = +{ + &remove_entity, + &store_headers, + &store_body, + &recall_headers, + &recall_body, + &create_entity, + &open_entity, + &remove_url, + &commit_entity, + &invalidate_entity +}; + +static void disk_cache_register_hook(apr_pool_t *p) +{ + /* cache initializer */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "disk", "0", + &cache_disk_provider); +} + +AP_DECLARE_MODULE(cache_disk) = { + STANDARD20_MODULE_STUFF, + create_dir_config, /* create per-directory config structure */ + merge_dir_config, /* 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_cache_disk.dep b/modules/cache/mod_cache_disk.dep new file mode 100644 index 0000000..c757a8d --- /dev/null +++ b/modules/cache/mod_cache_disk.dep @@ -0,0 +1,59 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_cache_disk.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_cache_disk.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_charset.h"\ + "..\..\include\util_filter.h"\ + "..\..\include\util_script.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_date.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apr_xlate.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_lib.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + ".\cache_common.h"\ + ".\cache_disk_common.h"\ + ".\mod_cache.h"\ + ".\mod_cache_disk.h"\ + diff --git a/modules/cache/mod_cache_disk.dsp b/modules/cache/mod_cache_disk.dsp new file mode 100644 index 0000000..19ca39e --- /dev/null +++ b/modules/cache/mod_cache_disk.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="mod_cache_disk" - 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_disk - 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_disk.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_disk.mak" CFG="mod_cache_disk - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_disk - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_disk - 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_disk - 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_cache_disk_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_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_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_disk.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_cache_disk.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_cache_disk - 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_cache_disk_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_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_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_disk.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_cache_disk.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_cache_disk - Win32 Release" +# Name "mod_cache_disk - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_cache_disk.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_cache_disk.h b/modules/cache/mod_cache_disk.h new file mode 100644 index 0000000..561ee3b --- /dev/null +++ b/modules/cache/mod_cache_disk.h @@ -0,0 +1,91 @@ +/* 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_CACHE_DISK_H +#define MOD_CACHE_DISK_H + +#include "apr_file_io.h" + +#include "cache_disk_common.h" + +/* + * include for mod_cache_disk: Disk Based HTTP 1.1 Cache. + */ + +typedef struct { + apr_pool_t *pool; + const char *file; + apr_file_t *fd; + char *tempfile; + apr_file_t *tempfd; +} disk_cache_file_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; + const char *prefix; + disk_cache_file_t data; /* data file structure */ + disk_cache_file_t hdrs; /* headers file structure */ + disk_cache_file_t vary; /* vary file structure */ + 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_off_t file_size; /* File size of the cached data file */ + disk_cache_info_t disk_info; /* Header information. */ + apr_table_t *headers_in; /* Input headers to save */ + apr_table_t *headers_out; /* Output headers to save */ + apr_off_t offset; /* Max size to set aside */ + apr_time_t timeout; /* Max time to set aside */ + unsigned int done:1; /* Is the attempt to cache complete? */ +} disk_cache_object_t; + + +/* + * mod_cache_disk configuration + */ +/* TODO: Make defaults OS specific */ +#define CACHEFILE_LEN 20 /* must be less than HASH_LEN/2 */ +#define DEFAULT_DIRLEVELS 2 +#define DEFAULT_DIRLENGTH 2 +#define DEFAULT_MIN_FILE_SIZE 1 +#define DEFAULT_MAX_FILE_SIZE 1000000 +#define DEFAULT_READSIZE 0 +#define DEFAULT_READTIME 0 + +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 */ +} disk_cache_conf; + +typedef struct { + apr_off_t minfs; /* minimum file size for cached files */ + apr_off_t maxfs; /* maximum file size for cached files */ + apr_off_t readsize; /* maximum data to attempt to cache in one go */ + apr_time_t readtime; /* maximum time taken to cache in one go */ + unsigned int minfs_set:1; + unsigned int maxfs_set:1; + unsigned int readsize_set:1; + unsigned int readtime_set:1; +} disk_cache_dir_conf; + +#endif /*MOD_CACHE_DISK_H*/ + diff --git a/modules/cache/mod_cache_disk.mak b/modules/cache/mod_cache_disk.mak new file mode 100644 index 0000000..5b4dd7a --- /dev/null +++ b/modules/cache/mod_cache_disk.mak @@ -0,0 +1,381 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache_disk.dsp +!IF "$(CFG)" == "" +CFG=mod_cache_disk - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_cache_disk - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_cache_disk - Win32 Release" && "$(CFG)" != "mod_cache_disk - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_disk.mak" CFG="mod_cache_disk - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_disk - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_disk - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "mod_cache - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_cache - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_cache_disk.obj" + -@erase "$(INTDIR)\mod_cache_disk.res" + -@erase "$(INTDIR)\mod_cache_disk_src.idb" + -@erase "$(INTDIR)\mod_cache_disk_src.pdb" + -@erase "$(OUTDIR)\mod_cache_disk.exp" + -@erase "$(OUTDIR)\mod_cache_disk.lib" + -@erase "$(OUTDIR)\mod_cache_disk.pdb" + -@erase "$(OUTDIR)\mod_cache_disk.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_disk_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_disk.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_disk.pdb" /debug /out:"$(OUTDIR)\mod_cache_disk.so" /implib:"$(OUTDIR)\mod_cache_disk.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_cache_disk.obj" \ + "$(INTDIR)\mod_cache_disk.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" \ + "$(OUTDIR)\mod_cache.lib" + +"$(OUTDIR)\mod_cache_disk.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_cache_disk.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_disk.so" + if exist .\Release\mod_cache_disk.so.manifest mt.exe -manifest .\Release\mod_cache_disk.so.manifest -outputresource:.\Release\mod_cache_disk.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "mod_cache - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache_disk.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_cache - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_cache_disk.obj" + -@erase "$(INTDIR)\mod_cache_disk.res" + -@erase "$(INTDIR)\mod_cache_disk_src.idb" + -@erase "$(INTDIR)\mod_cache_disk_src.pdb" + -@erase "$(OUTDIR)\mod_cache_disk.exp" + -@erase "$(OUTDIR)\mod_cache_disk.lib" + -@erase "$(OUTDIR)\mod_cache_disk.pdb" + -@erase "$(OUTDIR)\mod_cache_disk.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_disk_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_disk.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_disk.pdb" /debug /out:"$(OUTDIR)\mod_cache_disk.so" /implib:"$(OUTDIR)\mod_cache_disk.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_disk.so +LINK32_OBJS= \ + "$(INTDIR)\mod_cache_disk.obj" \ + "$(INTDIR)\mod_cache_disk.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" \ + "$(OUTDIR)\mod_cache.lib" + +"$(OUTDIR)\mod_cache_disk.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_cache_disk.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_disk.so" + if exist .\Debug\mod_cache_disk.so.manifest mt.exe -manifest .\Debug\mod_cache_disk.so.manifest -outputresource:.\Debug\mod_cache_disk.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_cache_disk.dep") +!INCLUDE "mod_cache_disk.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_cache_disk.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" || "$(CFG)" == "mod_cache_disk - Win32 Debug" + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + +"mod_cache - Win32 Release" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" + cd "." + +"mod_cache - Win32 ReleaseCLEAN" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" RECURSE=1 CLEAN + cd "." + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + +"mod_cache - Win32 Debug" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" + cd "." + +"mod_cache - Win32 DebugCLEAN" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" RECURSE=1 CLEAN + cd "." + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_cache_disk - Win32 Release" + + +"$(INTDIR)\mod_cache_disk.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_cache_disk - Win32 Debug" + + +"$(INTDIR)\mod_cache_disk.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_disk.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache_disk.so" /d LONG_NAME="cache_disk_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_cache_disk.c + +"$(INTDIR)\mod_cache_disk.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_cache_socache.c b/modules/cache/mod_cache_socache.c new file mode 100644 index 0000000..0d76760 --- /dev/null +++ b/modules/cache/mod_cache_socache.c @@ -0,0 +1,1543 @@ +/* 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_lib.h" +#include "apr_file_io.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_core.h" +#include "http_protocol.h" +#include "ap_provider.h" +#include "ap_socache.h" +#include "util_filter.h" +#include "util_script.h" +#include "util_charset.h" +#include "util_mutex.h" + +#include "mod_cache.h" +#include "mod_status.h" + +#include "cache_socache_common.h" + +/* + * mod_cache_socache: Shared Object Cache Based HTTP 1.1 Cache. + * + * Flow to Find the entry: + * Incoming client requests URI /foo/bar/baz + * Fetch URI key (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 key using HeaderName+HeaderValue+.../foo/bar/baz + * re-read in key (must be format #2) + * + * Format #1: + * apr_uint32_t format; + * apr_time_t expire; + * apr_array_t vary_headers (delimited by CRLF) + * + * Format #2: + * cache_socache_info_t (first sizeof(apr_uint32_t) bytes is the format) + * entity name (sobj->name) [length is in cache_socache_info_t->name_len] + * r->headers_out (delimited by CRLF) + * CRLF + * r->headers_in (delimited by CRLF) + * CRLF + */ + +module AP_MODULE_DECLARE_DATA cache_socache_module; + +/* + * cache_socache_object_t + * Pointed to by cache_object_t::vobj + */ +typedef struct cache_socache_object_t +{ + apr_pool_t *pool; /* pool */ + unsigned char *buffer; /* the cache buffer */ + apr_size_t buffer_len; /* size of the buffer */ + apr_bucket_brigade *body; /* brigade containing the body, if any */ + apr_table_t *headers_in; /* Input headers to save */ + apr_table_t *headers_out; /* Output headers to save */ + cache_socache_info_t socache_info; /* Header information. */ + apr_size_t body_offset; /* offset to the start of the body */ + apr_off_t body_length; /* length of the cached entity body */ + apr_time_t expire; /* when to expire the entry */ + + const char *name; /* Requested URI without vary bits - suitable for mortals. */ + const char *key; /* On-disk prefix; URI with Vary bits (if present) */ + apr_off_t offset; /* Max size to set aside */ + apr_time_t timeout; /* Max time to set aside */ + unsigned int newbody :1; /* whether a new body is present */ + unsigned int done :1; /* Is the attempt to cache complete? */ +} cache_socache_object_t; + +/* + * mod_cache_socache configuration + */ +#define DEFAULT_MAX_FILE_SIZE 100*1024 +#define DEFAULT_MAXTIME 86400 +#define DEFAULT_MINTIME 600 +#define DEFAULT_READSIZE 0 +#define DEFAULT_READTIME 0 + +typedef struct cache_socache_provider_conf +{ + const char *args; + ap_socache_provider_t *socache_provider; + ap_socache_instance_t *socache_instance; +} cache_socache_provider_conf; + +typedef struct cache_socache_conf +{ + cache_socache_provider_conf *provider; +} cache_socache_conf; + +typedef struct cache_socache_dir_conf +{ + apr_off_t max; /* maximum file size for cached files */ + apr_time_t maxtime; /* maximum expiry time */ + apr_time_t mintime; /* minimum expiry time */ + apr_off_t readsize; /* maximum data to attempt to cache in one go */ + apr_time_t readtime; /* maximum time taken to cache in one go */ + unsigned int max_set :1; + unsigned int maxtime_set :1; + unsigned int mintime_set :1; + unsigned int readsize_set :1; + unsigned int readtime_set :1; +} cache_socache_dir_conf; + +/* Shared object cache and mutex */ +static const char * const cache_socache_id = "cache-socache"; +static apr_global_mutex_t *socache_mutex = NULL; + +/* + * Local static functions + */ + +static apr_status_t read_array(request_rec *r, apr_array_header_t *arr, + unsigned char *buffer, apr_size_t buffer_len, apr_size_t *slider) +{ + apr_size_t val = *slider; + + while (*slider < buffer_len) { + if (buffer[*slider] == '\r') { + if (val == *slider) { + (*slider)++; + return APR_SUCCESS; + } + *((const char **) apr_array_push(arr)) = apr_pstrndup(r->pool, + (const char *) buffer + val, *slider - val); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + val = *slider; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_array(apr_array_header_t *arr, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + const char **elts; + + elts = (const char **) arr->elts; + + for (i = 0; i < arr->nelts; i++) { + apr_size_t e_len = strlen(elts[i]); + if (e_len + 3 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s" CRLF, elts[i]); + *slider += len; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static apr_status_t read_table(cache_handle_t *handle, request_rec *r, + apr_table_t *table, unsigned char *buffer, apr_size_t buffer_len, + apr_size_t *slider) +{ + apr_size_t key = *slider, colon = 0, len = 0; + + while (*slider < buffer_len) { + if (buffer[*slider] == ':') { + if (!colon) { + colon = *slider; + } + (*slider)++; + } + else if (buffer[*slider] == '\r') { + len = colon; + if (key == *slider) { + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + return APR_SUCCESS; + } + if (!colon || buffer[colon++] != ':') { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02344) + "Premature end of cache headers."); + return APR_EGENERAL; + } + /* Do not go past the \r from above as apr_isspace('\r') is true */ + while (apr_isspace(buffer[colon]) && (colon < *slider)) { + colon++; + } + apr_table_addn(table, apr_pstrndup(r->pool, (const char *) buffer + + key, len - key), apr_pstrndup(r->pool, + (const char *) buffer + colon, *slider - colon)); + (*slider)++; + if (buffer[*slider] == '\n') { + (*slider)++; + } + key = *slider; + colon = 0; + } + else if (buffer[*slider] == '\0') { + (*slider)++; + return APR_SUCCESS; + } + else { + (*slider)++; + } + } + + return APR_EOF; +} + +static apr_status_t store_table(apr_table_t *table, unsigned char *buffer, + apr_size_t buffer_len, apr_size_t *slider) +{ + int i, len; + 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) { + apr_size_t key_len = strlen(elts[i].key); + apr_size_t val_len = strlen(elts[i].val); + if (key_len + val_len + 5 >= buffer_len - *slider) { + return APR_EOF; + } + len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL, + buffer ? buffer_len - *slider : 0, "%s: %s" CRLF, + elts[i].key, elts[i].val); + *slider += len; + } + } + if (3 >= buffer_len - *slider) { + return APR_EOF; + } + if (buffer) { + memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1); + } + *slider += sizeof(CRLF) - 1; + + return APR_SUCCESS; +} + +static const char* regen_key(apr_pool_t *p, apr_table_t *headers, + apr_array_header_t *varray, const char *oldkey, + apr_size_t *newkeylen) +{ + 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, newkeylen); +} + +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, apr_bucket_brigade *bb) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj; + cache_socache_object_t *sobj; + apr_size_t total; + + if (conf->provider == NULL) { + return DECLINED; + } + + /* we don't support caching of range requests (yet) */ + /* TODO: but we could */ + if (r->status == HTTP_PARTIAL_CONTENT) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02345) + "URL %s partial content response not cached", + key); + return DECLINED; + } + + /* + * We have a chicken and egg problem. We don't know until we + * attempt to store_headers just how big the response will be + * and whether it will fit in the cache limits set. But we + * need to make a decision now as to whether we plan to try. + * If we make the wrong decision, we could prevent another + * cache implementation, such as cache_disk, from getting the + * opportunity to cache, and that would be unfortunate. + * + * In a series of tests, from cheapest to most expensive, + * decide whether or not to ignore this attempt to cache, + * with a small margin just to be sure. + */ + if (len < 0) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02346) + "URL '%s' had no explicit size, ignoring", key); + return DECLINED; + } + if (len > dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02347) + "URL '%s' body larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* estimate the total cached size, given current headers */ + total = len + sizeof(cache_socache_info_t) + strlen(key); + if (APR_SUCCESS != store_table(r->headers_out, NULL, dconf->max, &total) + || APR_SUCCESS != store_table(r->headers_in, NULL, dconf->max, + &total)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02348) + "URL '%s' estimated headers size larger than limit, ignoring " + "(%" APR_SIZE_T_FMT " > %" APR_OFF_T_FMT ")", + key, total, dconf->max); + return DECLINED; + } + + if (total >= dconf->max) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02349) + "URL '%s' body and headers larger than limit, ignoring " + "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")", + key, len, dconf->max); + return DECLINED; + } + + /* Allocate and initialize cache_object_t and cache_socache_object_t */ + h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj)); + obj->vobj = sobj = apr_pcalloc(r->pool, sizeof(*sobj)); + + obj->key = apr_pstrdup(r->pool, key); + sobj->key = obj->key; + sobj->name = obj->key; + + return OK; +} + +static int open_entity(cache_handle_t *h, request_rec *r, const char *key) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_uint32_t format; + apr_size_t slider; + unsigned int buffer_len; + const char *nkey; + apr_status_t rc; + cache_object_t *obj; + cache_info *info; + cache_socache_object_t *sobj; + apr_size_t len; + + nkey = NULL; + h->cache_obj = NULL; + + if (!conf->provider || !conf->provider->socache_instance) { + return DECLINED; + } + + /* Create and init the cache object */ + obj = apr_pcalloc(r->pool, sizeof(cache_object_t)); + sobj = apr_pcalloc(r->pool, sizeof(cache_socache_object_t)); + + info = &(obj->info); + + /* Create a temporary pool for the buffer, and destroy it if something + * goes wrong so we don't have large buffers of unused memory hanging + * about for the lifetime of the response. + */ + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max); + sobj->buffer_len = dconf->max; + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02350) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, (unsigned char *) key, + strlen(key), sobj->buffer, &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02351) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02352) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02353) + "Key found in cache but too big, ignoring: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + /* read the format from the cache file */ + memcpy(&format, sobj->buffer, sizeof(format)); + slider = sizeof(format); + + if (format == CACHE_SOCACHE_VARY_FORMAT_VERSION) { + apr_array_header_t* varray; + apr_time_t expire; + + memcpy(&expire, sobj->buffer + slider, sizeof(expire)); + slider += sizeof(expire); + + varray = apr_array_make(r->pool, 5, sizeof(char*)); + rc = read_array(r, varray, sobj->buffer, buffer_len, &slider); + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02354) + "Cannot parse vary entry for key: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + nkey = regen_key(r->pool, r->headers_in, varray, key, &len); + + /* attempt to retrieve the cached entry */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02355) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + buffer_len = sobj->buffer_len; + rc = conf->provider->socache_provider->retrieve( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, len, sobj->buffer, + &buffer_len, r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02356) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + if (rc != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02357) + "Key not found in cache: %s", key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + if (buffer_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02358) + "Key found in cache but too big, ignoring: %s", key); + goto fail; + } + + } + else if (format != CACHE_SOCACHE_DISK_FORMAT_VERSION) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02359) + "Key '%s' found in cache has version %d, expected %d, ignoring", + key, format, CACHE_SOCACHE_DISK_FORMAT_VERSION); + goto fail; + } + else { + nkey = key; + } + + obj->key = nkey; + sobj->key = nkey; + sobj->name = key; + + if (buffer_len >= sizeof(cache_socache_info_t)) { + memcpy(&sobj->socache_info, sobj->buffer, sizeof(cache_socache_info_t)); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02360) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + slider = sizeof(cache_socache_info_t); + + /* Store it away so we can get it later. */ + info->status = sobj->socache_info.status; + info->date = sobj->socache_info.date; + info->expire = sobj->socache_info.expire; + info->request_time = sobj->socache_info.request_time; + info->response_time = sobj->socache_info.response_time; + + memcpy(&info->control, &sobj->socache_info.control, sizeof(cache_control_t)); + + if (sobj->socache_info.name_len <= buffer_len - slider) { + if (strncmp((const char *) sobj->buffer + slider, sobj->name, + sobj->socache_info.name_len)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02361) + "Cache entry for key '%s' URL mismatch, ignoring", nkey); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + slider += sobj->socache_info.name_len; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02362) + "Cache entry for key '%s' too short, removing", nkey); + goto fail; + } + + /* Is this a cached HEAD request? */ + if (sobj->socache_info.header_only && !r->header_only) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02363) + "HEAD request cached, non-HEAD requested, ignoring: %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + + 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 */ + if (APR_SUCCESS != read_table(h, r, h->resp_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02364) + "Cache entry for key '%s' response headers unreadable, removing", nkey); + goto fail; + } + if (APR_SUCCESS != read_table(h, r, h->req_hdrs, sobj->buffer, buffer_len, + &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02365) + "Cache entry for key '%s' request headers unreadable, removing", nkey); + goto fail; + } + + /* Retrieve the body if we have one */ + sobj->body = apr_brigade_create(r->pool, r->connection->bucket_alloc); + len = buffer_len - slider; + + /* + * Optimisation: if the body is small, we want to make a + * copy of the body and free the temporary pool, as we + * don't want large blocks of unused memory hanging around + * to the end of the response. In contrast, if the body is + * large, we would rather leave the body where it is in the + * temporary pool, and save ourselves the copy. + */ + if (len * 2 > dconf->max) { + apr_bucket *e; + + /* large - use the brigade as is, we're done */ + e = apr_bucket_immortal_create((const char *) sobj->buffer + slider, + len, r->connection->bucket_alloc); + + APR_BRIGADE_INSERT_TAIL(sobj->body, e); + } + else { + + /* small - make a copy of the data... */ + apr_brigade_write(sobj->body, NULL, NULL, (const char *) sobj->buffer + + slider, len); + + /* ...and get rid of the large memory buffer */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + } + + /* make the configuration stick */ + h->cache_obj = obj; + obj->vobj = sobj; + + return OK; + +fail: + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02366) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove( + conf->provider->socache_instance, r->server, + (unsigned char *) nkey, strlen(nkey), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02367) + "could not release lock, ignoring: %s", obj->key); + } + } + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; +} + +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, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_socache_object_t *sobj; + + sobj = (cache_socache_object_t *) h->cache_obj->vobj; + if (!sobj) { + return DECLINED; + } + + /* Remove the key from the cache */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02368) + "could not acquire lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02369) + "could not release lock, ignoring: %s", sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return DECLINED; + } + } + + return OK; +} + +static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) +{ + /* we recalled the headers during open_entity, so do nothing */ + return APR_SUCCESS; +} + +static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p, + apr_bucket_brigade *bb) +{ + cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj; + apr_bucket *e; + + e = APR_BRIGADE_FIRST(sobj->body); + + if (e != APR_BRIGADE_SENTINEL(sobj->body)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(bb, e); + } + + return APR_SUCCESS; +} + +static apr_status_t store_headers(cache_handle_t *h, request_rec *r, + cache_info *info) +{ + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + apr_size_t slider; + apr_status_t rv; + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t*) obj->vobj; + cache_socache_info_t *socache_info; + + memcpy(&h->cache_obj->info, info, sizeof(cache_info)); + + if (r->headers_out) { + sobj->headers_out = ap_cache_cacheable_headers_out(r); + } + + if (r->headers_in) { + sobj->headers_in = ap_cache_cacheable_headers_in(r); + } + + sobj->expire + = obj->info.expire > r->request_time + dconf->maxtime ? r->request_time + + dconf->maxtime + : obj->info.expire + dconf->mintime; + + apr_pool_create(&sobj->pool, r->pool); + + sobj->buffer = apr_palloc(sobj->pool, dconf->max); + sobj->buffer_len = dconf->max; + socache_info = (cache_socache_info_t *) sobj->buffer; + + if (sobj->headers_out) { + const char *vary; + + vary = apr_table_get(sobj->headers_out, "Vary"); + + if (vary) { + apr_array_header_t* varray; + apr_uint32_t format = CACHE_SOCACHE_VARY_FORMAT_VERSION; + + memcpy(sobj->buffer, &format, sizeof(format)); + slider = sizeof(format); + + memcpy(sobj->buffer + slider, &obj->info.expire, + sizeof(obj->info.expire)); + slider += sizeof(obj->info.expire); + + varray = apr_array_make(r->pool, 6, sizeof(char*)); + tokens_to_array(r->pool, vary, varray); + + if (APR_SUCCESS != (rv = store_array(varray, sobj->buffer, + sobj->buffer_len, &slider))) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02370) + "buffer too small for Vary array, caching aborted: %s", + obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02371) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return status; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) obj->key, strlen(obj->key), sobj->expire, + (unsigned char *) sobj->buffer, (unsigned int) slider, + sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02372) + "could not release lock, ignoring: %s", obj->key); + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02373) + "Vary not written to cache, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray, + sobj->name, NULL); + } + } + + socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION; + socache_info->date = obj->info.date; + socache_info->expire = obj->info.expire; + socache_info->entity_version = sobj->socache_info.entity_version++; + socache_info->request_time = obj->info.request_time; + socache_info->response_time = obj->info.response_time; + socache_info->status = obj->info.status; + + if (r->header_only && r->status != HTTP_NOT_MODIFIED) { + socache_info->header_only = 1; + } + else { + socache_info->header_only = sobj->socache_info.header_only; + } + + socache_info->name_len = strlen(sobj->name); + + memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t)); + slider = sizeof(cache_socache_info_t); + + if (slider + socache_info->name_len >= sobj->buffer_len) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02374) + "cache buffer too small for name: %s", + sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + memcpy(sobj->buffer + slider, sobj->name, socache_info->name_len); + slider += socache_info->name_len; + + if (sobj->headers_out) { + if (APR_SUCCESS != store_table(sobj->headers_out, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02375) + "out-headers didn't fit in buffer: %s", sobj->name); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* Parse the vary header and dump those fields from the headers_in. */ + /* TODO: Make call to the same thing cache_select calls to crack Vary. */ + if (sobj->headers_in) { + if (APR_SUCCESS != store_table(sobj->headers_in, sobj->buffer, + sobj->buffer_len, &slider)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02376) + "in-headers didn't fit in buffer %s", + sobj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + sobj->body_offset = slider; + + return APR_SUCCESS; +} + +static apr_status_t store_body(cache_handle_t *h, request_rec *r, + apr_bucket_brigade *in, apr_bucket_brigade *out) +{ + apr_bucket *e; + apr_status_t rv = APR_SUCCESS; + cache_socache_object_t *sobj = + (cache_socache_object_t *) h->cache_obj->vobj; + cache_socache_dir_conf *dconf = + ap_get_module_config(r->per_dir_config, &cache_socache_module); + int seen_eos = 0; + + if (!sobj->offset) { + sobj->offset = dconf->readsize; + } + if (!sobj->timeout && dconf->readtime) { + sobj->timeout = apr_time_now() + dconf->readtime; + } + + if (!sobj->newbody) { + sobj->body_length = 0; + sobj->newbody = 1; + } + if (sobj->offset) { + apr_brigade_partition(in, sobj->offset, &e); + } + + while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) { + const char *str; + apr_size_t length; + + e = APR_BRIGADE_FIRST(in); + + /* are we done completely? if so, pass any trailing buckets right through */ + if (sobj->done || !sobj->pool) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* have we seen eos yet? */ + if (APR_BUCKET_IS_EOS(e)) { + seen_eos = 1; + sobj->done = 1; + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* honour flush buckets, we'll get called again */ + if (APR_BUCKET_IS_FLUSH(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + break; + } + + /* metadata buckets are preserved as is */ + if (APR_BUCKET_IS_METADATA(e)) { + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + continue; + } + + /* read the bucket, write to the cache */ + rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ); + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(out, e); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02377) + "Error when reading bucket for URL %s", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + + /* don't write empty buckets to the cache */ + if (!length) { + continue; + } + + sobj->body_length += length; + if (sobj->body_length >= sobj->buffer_len - sobj->body_offset) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02378) + "URL %s failed the buffer size check " + "(%" APR_OFF_T_FMT ">=%" APR_SIZE_T_FMT ")", + h->cache_obj->key, sobj->body_length, + sobj->buffer_len - sobj->body_offset); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + memcpy(sobj->buffer + sobj->body_offset + sobj->body_length - length, + str, length); + + /* have we reached the limit of how much we're prepared to write in one + * go? If so, leave, we'll get called again. This prevents us from trying + * to swallow too much data at once, or taking so long to write the data + * the client times out. + */ + sobj->offset -= length; + if (sobj->offset <= 0) { + sobj->offset = 0; + break; + } + if ((dconf->readtime && apr_time_now() > sobj->timeout)) { + sobj->timeout = 0; + break; + } + + } + + /* Was this the final bucket? If yes, perform sanity checks. + */ + if (seen_eos) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + + if (r->connection->aborted || r->no_cache) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380) + "Discarding body for URL %s " + "because connection has been aborted.", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + if (cl_header) { + apr_off_t cl; + char *cl_endp; + if (apr_strtoff(&cl, cl_header, &cl_endp, 10) != APR_SUCCESS + || *cl_endp != '\0' || cl != sobj->body_length) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381) + "URL %s didn't receive complete response, not caching", + h->cache_obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return APR_EGENERAL; + } + } + + /* All checks were fine, we're good to go when the commit comes */ + + } + + return APR_SUCCESS; +} + +static apr_status_t commit_entity(cache_handle_t *h, request_rec *r) +{ + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + cache_object_t *obj = h->cache_obj; + cache_socache_object_t *sobj = (cache_socache_object_t *) obj->vobj; + apr_status_t rv; + + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02384) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return status; + } + } + rv = conf->provider->socache_provider->store( + conf->provider->socache_instance, r->server, + (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire, + sobj->buffer, sobj->body_offset + sobj->body_length, sobj->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02385) + "could not release lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return status; + } + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02386) + "could not write to cache, ignoring: %s", sobj->key); + goto fail; + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02387) + "commit_entity: Headers and body for URL %s cached for maximum of %d seconds.", + sobj->name, (apr_uint32_t)apr_time_sec(sobj->expire - r->request_time)); + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + + return APR_SUCCESS; + +fail: + /* For safety, remove any existing entry on failure, just in case it could not + * be revalidated successfully. + */ + if (socache_mutex) { + apr_status_t status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02388) + "could not acquire lock, ignoring: %s", obj->key); + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; + } + } + conf->provider->socache_provider->remove(conf->provider->socache_instance, + r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool); + if (socache_mutex) { + apr_status_t status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02389) + "could not release lock, ignoring: %s", obj->key); + } + } + + apr_pool_destroy(sobj->pool); + sobj->pool = NULL; + return rv; +} + +static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r) +{ + /* mark the entity as invalidated */ + h->cache_obj->info.control.invalidated = 1; + + return commit_entity(h, r); +} + +static void *create_dir_config(apr_pool_t *p, char *dummy) +{ + cache_socache_dir_conf *dconf = + apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + + dconf->max = DEFAULT_MAX_FILE_SIZE; + dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME); + dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME); + dconf->readsize = DEFAULT_READSIZE; + dconf->readtime = DEFAULT_READTIME; + + return dconf; +} + +static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv) +{ + cache_socache_dir_conf + *new = + (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf)); + cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv; + cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev; + + new->max = (add->max_set == 0) ? base->max : add->max; + new->max_set = add->max_set || base->max_set; + new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime; + new->maxtime_set = add->maxtime_set || base->maxtime_set; + new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime; + new->mintime_set = add->mintime_set || base->mintime_set; + new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize; + new->readsize_set = add->readsize_set || base->readsize_set; + new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime; + new->readtime_set = add->readtime_set || base->readtime_set; + + return new; +} + +static void *create_config(apr_pool_t *p, server_rec *s) +{ + cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf)); + + return conf; +} + +static void *merge_config(apr_pool_t *p, void *basev, void *overridesv) +{ + cache_socache_conf *ps; + cache_socache_conf *base = (cache_socache_conf *) basev; + cache_socache_conf *overrides = (cache_socache_conf *) overridesv; + + /* socache server config only has one field */ + ps = overrides ? overrides : base; + + return ps; +} + +/* + * mod_cache_socache configuration directives handlers. + */ +static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr, + const char *arg) +{ + cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config, + &cache_socache_module); + cache_socache_provider_conf *provider = conf->provider + = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf)); + + const char *err = NULL, *sep, *name; + + /* Argument is of form 'name:args' or just 'name'. */ + sep = ap_strchr_c(arg, ':'); + if (sep) { + name = apr_pstrmemdup(cmd->pool, arg, sep - arg); + sep++; + provider->args = sep; + } + else { + name = arg; + } + + provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP, + name, AP_SOCACHE_PROVIDER_VERSION); + if (provider->socache_provider == NULL) { + err = apr_psprintf(cmd->pool, + "Unknown socache provider '%s'. Maybe you need " + "to load the appropriate socache module " + "(mod_socache_%s?)", name, name); + } + return err; +} + +static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS + || dconf->max < 1024 || dconf->max > APR_UINT32_MAX) { + return "CacheSocacheMaxSize argument must be a integer representing " + "the max size of a cached entry (headers and body), at least 1024 " + "and at most " APR_STRINGIFY(APR_UINT32_MAX); + } + dconf->max_set = 1; + return NULL; +} + +static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry."; + } + dconf->maxtime = apr_time_from_sec(seconds); + dconf->maxtime_set = 1; + return NULL; +} + +static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t seconds; + + if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) { + return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry."; + } + dconf->mintime = apr_time_from_sec(seconds); + dconf->mintime_set = 1; + return NULL; +} + +static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + + if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS + || dconf->readsize < 0) { + return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go."; + } + dconf->readsize_set = 1; + return NULL; +} + +static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr, + const char *arg) +{ + cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr; + apr_off_t milliseconds; + + if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS + || milliseconds < 0) { + return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go."; + } + dconf->readtime = apr_time_from_msec(milliseconds); + dconf->readtime_set = 1; + return NULL; +} + +static apr_status_t remove_lock(void *data) +{ + if (socache_mutex) { + apr_global_mutex_destroy(socache_mutex); + socache_mutex = NULL; + } + return APR_SUCCESS; +} + +static apr_status_t destroy_cache(void *data) +{ + server_rec *s = data; + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + if (conf->provider && conf->provider->socache_instance) { + conf->provider->socache_provider->destroy( + conf->provider->socache_instance, s); + conf->provider->socache_instance = NULL; + } + return APR_SUCCESS; +} + +static int socache_status_hook(request_rec *r, int flags) +{ + apr_status_t status = APR_SUCCESS; + cache_socache_conf *conf = ap_get_module_config(r->server->module_config, + &cache_socache_module); + if (!conf->provider || !conf->provider->socache_provider || + !conf->provider->socache_instance) { + return DECLINED; + } + + if (!(flags & AP_STATUS_SHORT)) { + ap_rputs("<hr>\n" + "<table cellspacing=0 cellpadding=0>\n" + "<tr><td bgcolor=\"#000000\">\n" + "<b><font color=\"#ffffff\" face=\"Arial,Helvetica\">" + "mod_cache_socache Status:</font></b>\n" + "</td></tr>\n" + "<tr><td bgcolor=\"#ffffff\">\n", r); + } + else { + ap_rputs("ModCacheSocacheStatus\n", r); + } + + if (socache_mutex) { + status = apr_global_mutex_lock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02816) + "could not acquire lock for cache status"); + } + } + + if (status != APR_SUCCESS) { + if (!(flags & AP_STATUS_SHORT)) { + ap_rputs("No cache status data available\n", r); + } + else { + ap_rputs("NotAvailable\n", r); + } + } else { + conf->provider->socache_provider->status(conf->provider->socache_instance, + r, flags); + } + + if (socache_mutex && status == APR_SUCCESS) { + status = apr_global_mutex_unlock(socache_mutex); + if (status != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02817) + "could not release lock for cache status"); + } + } + + if (!(flags & AP_STATUS_SHORT)) { + ap_rputs("</td></tr>\n</table>\n", r); + } + return OK; +} + +static void socache_status_register(apr_pool_t *p) +{ + APR_OPTIONAL_HOOK(ap, status_hook, socache_status_hook, NULL, NULL, APR_HOOK_MIDDLE); +} + +static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp) +{ + apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL, + APR_LOCK_DEFAULT, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02390) + "failed to register %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + + /* Register to handle mod_status status page generation */ + socache_status_register(pconf); + + return OK; +} + +static int socache_post_config(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptmp, server_rec *base_server) +{ + server_rec *s; + apr_status_t rv; + const char *errmsg; + static struct ap_socache_hints socache_hints = + { 64, 2048, 60000000 }; + + for (s = base_server; s; s = s->next) { + cache_socache_conf *conf = + ap_get_module_config(s->module_config, &cache_socache_module); + + if (!conf->provider) { + continue; + } + + if (!socache_mutex && conf->provider->socache_provider->flags + & AP_SOCACHE_FLAG_NOTMPSAFE) { + + rv = ap_global_mutex_create(&socache_mutex, NULL, cache_socache_id, + NULL, s, pconf, 0); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02391) + "failed to create %s mutex", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, NULL, remove_lock, + apr_pool_cleanup_null); + } + + errmsg = conf->provider->socache_provider->create( + &conf->provider->socache_instance, conf->provider->args, ptmp, + pconf); + if (errmsg) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog, + APLOGNO(02392) "%s", errmsg); + return 500; /* An HTTP status would be a misnomer! */ + } + + rv = conf->provider->socache_provider->init( + conf->provider->socache_instance, cache_socache_id, + &socache_hints, s, pconf); + if (rv != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02393) + "failed to initialise %s cache", cache_socache_id); + return 500; /* An HTTP status would be a misnomer! */ + } + apr_pool_cleanup_register(pconf, (void *) s, destroy_cache, + apr_pool_cleanup_null); + + } + + return OK; +} + +static void socache_child_init(apr_pool_t *p, server_rec *s) +{ + const char *lock; + apr_status_t rv; + if (!socache_mutex) { + return; /* don't waste the overhead of creating mutex & cache */ + } + lock = apr_global_mutex_lockfile(socache_mutex); + rv = apr_global_mutex_child_init(&socache_mutex, lock, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02394) + "failed to initialise mutex in child_init"); + } +} + +static const command_rec cache_socache_cmds[] = +{ + AP_INIT_TAKE1("CacheSocache", set_cache_socache, NULL, RSRC_CONF, + "The shared object cache to store cache files"), + AP_INIT_TAKE1("CacheSocacheMaxTime", set_cache_maxtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMinTime", set_cache_mintime, NULL, RSRC_CONF | ACCESS_CONF, + "The minimum cache expiry age to cache a document in seconds"), + AP_INIT_TAKE1("CacheSocacheMaxSize", set_cache_max, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum cache entry size (headers and body) to cache a document"), + AP_INIT_TAKE1("CacheSocacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum quantity of data to attempt to read and cache in one go"), + AP_INIT_TAKE1("CacheSocacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF, + "The maximum time taken to attempt to read and cache in go"), + { NULL } +}; + +static const cache_provider cache_socache_provider = +{ + &remove_entity, &store_headers, &store_body, &recall_headers, &recall_body, + &create_entity, &open_entity, &remove_url, &commit_entity, + &invalidate_entity +}; + +static void cache_socache_register_hook(apr_pool_t *p) +{ + /* cache initializer */ + ap_register_provider(p, CACHE_PROVIDER_GROUP, "socache", "0", + &cache_socache_provider); + ap_hook_pre_config(socache_precfg, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(socache_post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(socache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +AP_DECLARE_MODULE(cache_socache) = { STANDARD20_MODULE_STUFF, + create_dir_config, /* create per-directory config structure */ + merge_dir_config, /* merge per-directory config structures */ + create_config, /* create per-server config structure */ + merge_config, /* merge per-server config structures */ + cache_socache_cmds, /* command apr_table_t */ + cache_socache_register_hook /* register hooks */ +}; diff --git a/modules/cache/mod_cache_socache.dep b/modules/cache/mod_cache_socache.dep new file mode 100644 index 0000000..d202b40 --- /dev/null +++ b/modules/cache/mod_cache_socache.dep @@ -0,0 +1,67 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_cache_socache.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_cache_socache.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\ap_socache.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_charset.h"\ + "..\..\include\util_filter.h"\ + "..\..\include\util_mutex.h"\ + "..\..\include\util_script.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_date.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apr_xlate.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_lib.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + "..\generators\mod_status.h"\ + ".\cache_common.h"\ + ".\cache_socache_common.h"\ + ".\mod_cache.h"\ + diff --git a/modules/cache/mod_cache_socache.dsp b/modules/cache/mod_cache_socache.dsp new file mode 100644 index 0000000..1dd8214 --- /dev/null +++ b/modules/cache/mod_cache_socache.dsp @@ -0,0 +1,115 @@ +# Microsoft Developer Studio Project File - Name="mod_cache_socache" - 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_socache - 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_socache.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_socache.mak" CFG="mod_cache_socache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_socache - 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_socache - 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" /I "../generators" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fd"Release\mod_cache_socache_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_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_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_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_cache_socache - 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" /I "../generators" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fd"Debug\mod_cache_socache_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_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_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_socache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_cache_socache - Win32 Release" +# Name "mod_cache_socache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_cache.h +# End Source File +# Begin Source File + +SOURCE=.\mod_cache_socache.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_cache_socache.mak b/modules/cache/mod_cache_socache.mak new file mode 100644 index 0000000..7857e7f --- /dev/null +++ b/modules/cache/mod_cache_socache.mak @@ -0,0 +1,381 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_cache_socache.dsp +!IF "$(CFG)" == "" +CFG=mod_cache_socache - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_cache_socache - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_cache_socache - Win32 Release" && "$(CFG)" != "mod_cache_socache - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_socache.mak" CFG="mod_cache_socache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_cache_socache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_cache_socache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "mod_cache - Win32 Release" "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" "mod_cache - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_cache_socache.obj" + -@erase "$(INTDIR)\mod_cache_socache.res" + -@erase "$(INTDIR)\mod_cache_socache_src.idb" + -@erase "$(INTDIR)\mod_cache_socache_src.pdb" + -@erase "$(OUTDIR)\mod_cache_socache.exp" + -@erase "$(OUTDIR)\mod_cache_socache.lib" + -@erase "$(OUTDIR)\mod_cache_socache.pdb" + -@erase "$(OUTDIR)\mod_cache_socache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_socache_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_socache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_socache.pdb" /debug /out:"$(OUTDIR)\mod_cache_socache.so" /implib:"$(OUTDIR)\mod_cache_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_cache_socache.obj" \ + "$(INTDIR)\mod_cache_socache.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" \ + "$(OUTDIR)\mod_cache.lib" + +"$(OUTDIR)\mod_cache_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_socache.so" + if exist .\Release\mod_cache_socache.so.manifest mt.exe -manifest .\Release\mod_cache_socache.so.manifest -outputresource:.\Release\mod_cache_socache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "mod_cache - Win32 Debug" "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_cache_socache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" "mod_cache - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_cache_socache.obj" + -@erase "$(INTDIR)\mod_cache_socache.res" + -@erase "$(INTDIR)\mod_cache_socache_src.idb" + -@erase "$(INTDIR)\mod_cache_socache_src.pdb" + -@erase "$(OUTDIR)\mod_cache_socache.exp" + -@erase "$(OUTDIR)\mod_cache_socache.lib" + -@erase "$(OUTDIR)\mod_cache_socache.pdb" + -@erase "$(OUTDIR)\mod_cache_socache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_cache_socache_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_cache_socache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_cache_socache.pdb" /debug /out:"$(OUTDIR)\mod_cache_socache.so" /implib:"$(OUTDIR)\mod_cache_socache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_cache_socache.so +LINK32_OBJS= \ + "$(INTDIR)\mod_cache_socache.obj" \ + "$(INTDIR)\mod_cache_socache.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" \ + "$(OUTDIR)\mod_cache.lib" + +"$(OUTDIR)\mod_cache_socache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_cache_socache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_cache_socache.so" + if exist .\Debug\mod_cache_socache.so.manifest mt.exe -manifest .\Debug\mod_cache_socache.so.manifest -outputresource:.\Debug\mod_cache_socache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_cache_socache.dep") +!INCLUDE "mod_cache_socache.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_cache_socache.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" || "$(CFG)" == "mod_cache_socache - Win32 Debug" + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + +"mod_cache - Win32 Release" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" + cd "." + +"mod_cache - Win32 ReleaseCLEAN" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Release" RECURSE=1 CLEAN + cd "." + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + +"mod_cache - Win32 Debug" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" + cd "." + +"mod_cache - Win32 DebugCLEAN" : + cd "." + $(MAKE) /$(MAKEFLAGS) /F ".\mod_cache.mak" CFG="mod_cache - Win32 Debug" RECURSE=1 CLEAN + cd "." + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_cache_socache - Win32 Release" + + +"$(INTDIR)\mod_cache_socache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_cache_socache - Win32 Debug" + + +"$(INTDIR)\mod_cache_socache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_cache_socache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_cache_socache.so" /d LONG_NAME="cache_socache_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_cache_socache.c + +"$(INTDIR)\mod_cache_socache.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_file_cache.c b/modules/cache/mod_file_cache.c new file mode 100644 index 0000000..4199361 --- /dev/null +++ b/modules/cache/mod_file_cache.c @@ -0,0 +1,414 @@ +/* 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 + +#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, APLOGNO(00794) + "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, APLOGNO(00795) + "unable to stat(%s), skipping", fspec); + return; + } + if (tmp.finfo.filetype != APR_REG) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00796) + "%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, APLOGNO(00797) + "%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, APLOGNO(00798) + "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, APLOGNO(00799) + "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, APLOGNO(00800) + "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, APLOGNO(00801) + "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 AP_FILTER_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); + + apr_brigade_insert_file(bb, file->file, 0, file->finfo.size, r->pool); + + 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 AP_FILTER_ERROR; +#endif + return OK; +} + +static int file_cache_handler(request_rec *r) +{ + a_file *match; + int errstatus; + int rc = OK; + + /* Bail out if r->handler isn't the default value, and doesn't look like a Content-Type + * XXX: Even though we made the user explicitly list each path to cache? + */ + if (ap_strcmp_match(r->handler, "*/*") && !AP_IS_DEFAULT_HANDLER_NAME(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_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); + + ap_set_etag(r); + if ((errstatus = ap_meets_conditions(r)) != OK) { + return errstatus; + } + + /* 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); + */ + +} + +AP_DECLARE_MODULE(file_cache) = +{ + 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.dep b/modules/cache/mod_file_cache.dep new file mode 100644 index 0000000..3d47099 --- /dev/null +++ b/modules/cache/mod_file_cache.dep @@ -0,0 +1,56 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_file_cache.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_file_cache.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_expr.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_core.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + diff --git a/modules/cache/mod_file_cache.dsp b/modules/cache/mod_file_cache.dsp new file mode 100644 index 0000000..3c6afa4 --- /dev/null +++ b/modules/cache/mod_file_cache.dsp @@ -0,0 +1,111 @@ +# 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 +# Begin Special Build Tool +TargetPath=.\Release\mod_file_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!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 +# Begin Special Build Tool +TargetPath=.\Debug\mod_file_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!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 0000000..23b092a --- /dev/null +++ b/modules/cache/mod_file_cache.exp @@ -0,0 +1 @@ +file_cache_module diff --git a/modules/cache/mod_file_cache.mak b/modules/cache/mod_file_cache.mak new file mode 100644 index 0000000..0f54dc2 --- /dev/null +++ b/modules/cache/mod_file_cache.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_file_cache.dsp +!IF "$(CFG)" == "" +CFG=mod_file_cache - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_file_cache - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_file_cache - Win32 Release" && "$(CFG)" != "mod_file_cache - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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 +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_file_cache.obj" + -@erase "$(INTDIR)\mod_file_cache.res" + -@erase "$(INTDIR)\mod_file_cache_src.idb" + -@erase "$(INTDIR)\mod_file_cache_src.pdb" + -@erase "$(OUTDIR)\mod_file_cache.exp" + -@erase "$(OUTDIR)\mod_file_cache.lib" + -@erase "$(OUTDIR)\mod_file_cache.pdb" + -@erase "$(OUTDIR)\mod_file_cache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_file_cache_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\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 +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_file_cache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=/nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_file_cache.pdb" /debug /out:"$(OUTDIR)\mod_file_cache.so" /implib:"$(OUTDIR)\mod_file_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_file_cache.obj" \ + "$(INTDIR)\mod_file_cache.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_file_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_file_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_file_cache.so" + if exist .\Release\mod_file_cache.so.manifest mt.exe -manifest .\Release\mod_file_cache.so.manifest -outputresource:.\Release\mod_file_cache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_file_cache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_file_cache.obj" + -@erase "$(INTDIR)\mod_file_cache.res" + -@erase "$(INTDIR)\mod_file_cache_src.idb" + -@erase "$(INTDIR)\mod_file_cache_src.pdb" + -@erase "$(OUTDIR)\mod_file_cache.exp" + -@erase "$(OUTDIR)\mod_file_cache.lib" + -@erase "$(OUTDIR)\mod_file_cache.pdb" + -@erase "$(OUTDIR)\mod_file_cache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_file_cache_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\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 +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_file_cache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=/nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_file_cache.pdb" /debug /out:"$(OUTDIR)\mod_file_cache.so" /implib:"$(OUTDIR)\mod_file_cache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_file_cache.so +LINK32_OBJS= \ + "$(INTDIR)\mod_file_cache.obj" \ + "$(INTDIR)\mod_file_cache.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_file_cache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_file_cache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_file_cache.so" + if exist .\Debug\mod_file_cache.so.manifest mt.exe -manifest .\Debug\mod_file_cache.so.manifest -outputresource:.\Debug\mod_file_cache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_file_cache.dep") +!INCLUDE "mod_file_cache.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_file_cache.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" || "$(CFG)" == "mod_file_cache - Win32 Debug" + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_file_cache - Win32 Release" + + +"$(INTDIR)\mod_file_cache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_file_cache - Win32 Debug" + + +"$(INTDIR)\mod_file_cache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_file_cache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_file_cache.so" /d LONG_NAME="file_cache_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_file_cache.c + +"$(INTDIR)\mod_file_cache.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_socache_dbm.c b/modules/cache/mod_socache_dbm.c new file mode 100644 index 0000000..579d2ff --- /dev/null +++ b/modules/cache/mod_socache_dbm.c @@ -0,0 +1,595 @@ +/* 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 "httpd.h" +#include "http_log.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_config.h" +#include "mpm_common.h" +#include "mod_status.h" + +#include "apr.h" +#include "apr_strings.h" +#include "apr_time.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_dbm.h" + +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "ap_socache.h" + +#if AP_NEED_SET_MUTEX_PERMS +#include "unixd.h" +#endif + +/* Use of the context structure must be thread-safe after the initial + * create/init; callers must hold the mutex. */ +struct ap_socache_instance_t { + const char *data_file; + /* Pool must only be used with the mutex held. */ + apr_pool_t *pool; + apr_time_t last_expiry; + apr_interval_time_t expiry_interval; +}; + +/** + * Support for DBM library + */ +#define DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD ) + +#define DEFAULT_DBM_PREFIX "socache-dbm-" + +/* ### this should use apr_dbm_usednames. */ +#if !defined(DBM_FILE_SUFFIX_DIR) && !defined(DBM_FILE_SUFFIX_PAG) +#if defined(DBM_SUFFIX) +#define DBM_FILE_SUFFIX_DIR DBM_SUFFIX +#define DBM_FILE_SUFFIX_PAG DBM_SUFFIX +#elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM)) +#define DBM_FILE_SUFFIX_DIR ".db" +#define DBM_FILE_SUFFIX_PAG ".db" +#else +#define DBM_FILE_SUFFIX_DIR ".dir" +#define DBM_FILE_SUFFIX_PAG ".pag" +#endif +#endif + +static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s); + +static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_pool_t *p); + +static const char *socache_dbm_create(ap_socache_instance_t **context, + const char *arg, + apr_pool_t *tmp, apr_pool_t *p) +{ + ap_socache_instance_t *ctx; + + *context = ctx = apr_pcalloc(p, sizeof *ctx); + + if (arg && *arg) { + ctx->data_file = ap_server_root_relative(p, arg); + if (!ctx->data_file) { + return apr_psprintf(tmp, "Invalid cache file path %s", arg); + } + } + + apr_pool_create(&ctx->pool, p); + + return NULL; +} + +#if AP_NEED_SET_MUTEX_PERMS +static int try_chown(apr_pool_t *p, server_rec *s, + const char *name, const char *suffix) +{ + if (suffix) + name = apr_pstrcat(p, name, suffix, NULL); + if (-1 == chown(name, ap_unixd_config.user_id, + (gid_t)-1 /* no gid change */ )) + { + if (errno != ENOENT) + ap_log_error(APLOG_MARK, APLOG_ERR, APR_FROM_OS_ERROR(errno), s, APLOGNO(00802) + "Can't change owner of %s", name); + return -1; + } + return 0; +} +#endif + + +static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx, + const char *namespace, + const struct ap_socache_hints *hints, + server_rec *s, apr_pool_t *p) +{ + apr_dbm_t *dbm; + apr_status_t rv; + + /* for the DBM we need the data file */ + if (ctx->data_file == NULL) { + const char *path = apr_pstrcat(p, DEFAULT_DBM_PREFIX, namespace, + NULL); + + ctx->data_file = ap_runtime_dir_relative(p, path); + + if (ctx->data_file == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00803) + "could not use default path '%s' for DBM socache", + path); + return APR_EINVAL; + } + } + + /* open it once to create it and to make sure it _can_ be created */ + apr_pool_clear(ctx->pool); + + if ((rv = apr_dbm_open(&dbm, ctx->data_file, + APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00804) + "Cannot create socache DBM file `%s'", + ctx->data_file); + return rv; + } + apr_dbm_close(dbm); + + ctx->expiry_interval = (hints && hints->expiry_interval + ? hints->expiry_interval : apr_time_from_sec(30)); + +#if AP_NEED_SET_MUTEX_PERMS + /* + * We have to make sure the Apache child processes have access to + * the DBM file. But because there are brain-dead platforms where we + * cannot exactly determine the suffixes we try all possibilities. + */ + if (geteuid() == 0 /* is superuser */) { + try_chown(p, s, ctx->data_file, NULL); + if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_DIR)) + if (try_chown(p, s, ctx->data_file, ".db")) + try_chown(p, s, ctx->data_file, ".dir"); + if (try_chown(p, s, ctx->data_file, DBM_FILE_SUFFIX_PAG)) + if (try_chown(p, s, ctx->data_file, ".db")) + try_chown(p, s, ctx->data_file, ".pag"); + } +#endif + socache_dbm_expire(ctx, s); + + return APR_SUCCESS; +} + +static void socache_dbm_destroy(ap_socache_instance_t *ctx, server_rec *s) +{ + /* the correct way */ + unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_DIR, NULL)); + unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_PAG, NULL)); + /* the additional ways to be sure */ + unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL)); + unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL)); + unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL)); + unlink(ctx->data_file); +} + +static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_time_t expiry, + unsigned char *ucaData, + unsigned int nData, apr_pool_t *pool) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_datum_t dbmval; + apr_status_t rv; + + /* be careful: do not try to store too much bytes in a DBM file! */ +#ifdef PAIRMAX + if ((idlen + nData) >= PAIRMAX) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00805) + "data size too large for DBM socache: %d >= %d", + (idlen + nData), PAIRMAX); + return APR_ENOSPC; + } +#else + if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00806) + "data size too large for DBM socache: %d >= %d", + (idlen + nData), 950); + return APR_ENOSPC; + } +#endif + + /* create DBM key */ + dbmkey.dptr = (char *)id; + dbmkey.dsize = idlen; + + /* create DBM value */ + dbmval.dsize = sizeof(apr_time_t) + nData; + dbmval.dptr = (char *)ap_malloc(dbmval.dsize); + memcpy((char *)dbmval.dptr, &expiry, sizeof(apr_time_t)); + memcpy((char *)dbmval.dptr+sizeof(apr_time_t), ucaData, nData); + + /* and store it to the DBM file */ + apr_pool_clear(ctx->pool); + + if ((rv = apr_dbm_open(&dbm, ctx->data_file, + APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00807) + "Cannot open socache DBM file `%s' for writing " + "(store)", + ctx->data_file); + free(dbmval.dptr); + return rv; + } + if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00808) + "Cannot store socache object to DBM file `%s'", + ctx->data_file); + apr_dbm_close(dbm); + free(dbmval.dptr); + return rv; + } + apr_dbm_close(dbm); + + /* free temporary buffers */ + free(dbmval.dptr); + + /* allow the regular expiring to occur */ + socache_dbm_expire(ctx, s); + + return APR_SUCCESS; +} + +static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen, + apr_pool_t *p) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_datum_t dbmval; + unsigned int nData; + apr_time_t expiry; + apr_time_t now; + apr_status_t rc; + + /* allow the regular expiring to occur */ + socache_dbm_expire(ctx, s); + + /* create DBM key and values */ + dbmkey.dptr = (char *)id; + dbmkey.dsize = idlen; + + /* and fetch it from the DBM file + * XXX: Should we open the dbm against r->pool so the cleanup will + * do the apr_dbm_close? This would make the code a bit cleaner. + */ + apr_pool_clear(ctx->pool); + if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00809) + "Cannot open socache DBM file `%s' for reading " + "(fetch)", + ctx->data_file); + return rc; + } + rc = apr_dbm_fetch(dbm, dbmkey, &dbmval); + if (rc != APR_SUCCESS) { + apr_dbm_close(dbm); + return APR_NOTFOUND; + } + if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(apr_time_t)) { + apr_dbm_close(dbm); + return APR_EGENERAL; + } + + /* parse resulting data */ + nData = dbmval.dsize-sizeof(apr_time_t); + if (nData > *destlen) { + apr_dbm_close(dbm); + return APR_ENOSPC; + } + + *destlen = nData; + memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); + memcpy(dest, (char *)dbmval.dptr + sizeof(apr_time_t), nData); + + apr_dbm_close(dbm); + + /* make sure the stuff is still not expired */ + now = apr_time_now(); + if (expiry <= now) { + socache_dbm_remove(ctx, s, id, idlen, p); + return APR_NOTFOUND; + } + + return APR_SUCCESS; +} + +static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_pool_t *p) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_status_t rv; + + /* create DBM key and values */ + dbmkey.dptr = (char *)id; + dbmkey.dsize = idlen; + + /* and delete it from the DBM file */ + apr_pool_clear(ctx->pool); + + if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00810) + "Cannot open socache DBM file `%s' for writing " + "(delete)", + ctx->data_file); + return rv; + } + apr_dbm_delete(dbm, dbmkey); + apr_dbm_close(dbm); + + return APR_SUCCESS; +} + +static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_datum_t dbmval; + apr_time_t expiry; + int elts = 0; + int deleted = 0; + int expired; + apr_datum_t *keylist; + int keyidx; + int i; + apr_time_t now; + apr_status_t rv; + + /* + * make sure the expiration for still not-accessed + * socache entries is done only from time to time + */ + now = apr_time_now(); + + if (now < ctx->last_expiry + ctx->expiry_interval) { + return; + } + + ctx->last_expiry = now; + + /* + * Here we have to be very carefully: Not all DBM libraries are + * smart enough to allow one to iterate over the elements and at the + * same time delete expired ones. Some of them get totally crazy + * while others have no problems. So we have to do it the slower but + * more safe way: we first iterate over all elements and remember + * those which have to be expired. Then in a second pass we delete + * all those expired elements. Additionally we reopen the DBM file + * to be really safe in state. + */ + +#define KEYMAX 1024 + + for (;;) { + /* allocate the key array in a memory sub pool */ + apr_pool_clear(ctx->pool); + + if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) { + break; + } + + /* pass 1: scan DBM database */ + keyidx = 0; + if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00811) + "Cannot open socache DBM file `%s' for " + "scanning", + ctx->data_file); + break; + } + apr_dbm_firstkey(dbm, &dbmkey); + while (dbmkey.dptr != NULL) { + elts++; + expired = FALSE; + apr_dbm_fetch(dbm, dbmkey, &dbmval); + if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL) + expired = TRUE; + else { + memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); + if (expiry <= now) + expired = TRUE; + } + if (expired) { + if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) { + keylist[keyidx].dsize = dbmkey.dsize; + keyidx++; + if (keyidx == KEYMAX) + break; + } + } + apr_dbm_nextkey(dbm, &dbmkey); + } + apr_dbm_close(dbm); + + /* pass 2: delete expired elements */ + if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00812) + "Cannot re-open socache DBM file `%s' for " + "expiring", + ctx->data_file); + break; + } + for (i = 0; i < keyidx; i++) { + apr_dbm_delete(dbm, keylist[i]); + deleted++; + } + apr_dbm_close(dbm); + + if (keyidx < KEYMAX) + break; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00813) + "DBM socache expiry: " + "old: %d, new: %d, removed: %d", + elts, elts-deleted, deleted); +} + +static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r, + int flags) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_datum_t dbmval; + int elts; + long size; + int avg; + apr_status_t rv; + + elts = 0; + size = 0; + + apr_pool_clear(ctx->pool); + if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00814) + "Cannot open socache DBM file `%s' for status " + "retrival", + ctx->data_file); + return; + } + /* + * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD + */ + apr_dbm_firstkey(dbm, &dbmkey); + for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) { + apr_dbm_fetch(dbm, dbmkey, &dbmval); + if (dbmval.dptr == NULL) + continue; + elts += 1; + size += dbmval.dsize; + } + apr_dbm_close(dbm); + if (size > 0 && elts > 0) + avg = (int)(size / (long)elts); + else + avg = 0; + if (!(flags & AP_STATUS_SHORT)) { + ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>"); + ap_rprintf(r, "current entries: <b>%d</b>, current size: <b>%ld</b> bytes<br>", elts, size); + ap_rprintf(r, "average entry size: <b>%d</b> bytes<br>", avg); + } + else { + ap_rputs("CacheType: DBM\n", r); + ap_rputs("CacheMaximumSize: unlimited\n", r); + ap_rprintf(r, "CacheCurrentEntries: %d\n", elts); + ap_rprintf(r, "CacheCurrentSize: %ld\n", size); + ap_rprintf(r, "CacheAvgEntrySize: %d\n", avg); + } +} + +static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx, + server_rec *s, void *userctx, + ap_socache_iterator_t *iterator, + apr_pool_t *pool) +{ + apr_dbm_t *dbm; + apr_datum_t dbmkey; + apr_datum_t dbmval; + apr_time_t expiry; + int expired; + apr_time_t now; + apr_status_t rv; + + /* + * make sure the expired records are omitted + */ + now = apr_time_now(); + if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, + DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00815) + "Cannot open socache DBM file `%s' for " + "iterating", ctx->data_file); + return rv; + } + rv = apr_dbm_firstkey(dbm, &dbmkey); + while (rv == APR_SUCCESS && dbmkey.dptr != NULL) { + expired = FALSE; + apr_dbm_fetch(dbm, dbmkey, &dbmval); + if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL) + expired = TRUE; + else { + memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t)); + if (expiry <= now) + expired = TRUE; + } + if (!expired) { + rv = iterator(ctx, s, userctx, + (unsigned char *)dbmkey.dptr, dbmkey.dsize, + (unsigned char *)dbmval.dptr + sizeof(apr_time_t), + dbmval.dsize - sizeof(apr_time_t), pool); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00816) + "dbm `%s' entry iterated", ctx->data_file); + if (rv != APR_SUCCESS) + return rv; + } + rv = apr_dbm_nextkey(dbm, &dbmkey); + } + apr_dbm_close(dbm); + + if (rv != APR_SUCCESS && rv != APR_EOF) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00817) + "Failure reading first/next socache DBM file `%s' record", + ctx->data_file); + return rv; + } + return APR_SUCCESS; +} + +static const ap_socache_provider_t socache_dbm = { + "dbm", + AP_SOCACHE_FLAG_NOTMPSAFE, + socache_dbm_create, + socache_dbm_init, + socache_dbm_destroy, + socache_dbm_store, + socache_dbm_retrieve, + socache_dbm_remove, + socache_dbm_status, + socache_dbm_iterate +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm", + AP_SOCACHE_PROVIDER_VERSION, + &socache_dbm); +} + +AP_DECLARE_MODULE(socache_dbm) = { + STANDARD20_MODULE_STUFF, + NULL, NULL, NULL, NULL, NULL, + register_hooks +}; diff --git a/modules/cache/mod_socache_dbm.dep b/modules/cache/mod_socache_dbm.dep new file mode 100644 index 0000000..8288f2e --- /dev/null +++ b/modules/cache/mod_socache_dbm.dep @@ -0,0 +1,60 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_socache_dbm.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_socache_dbm.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_mpm.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\ap_socache.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\httpd.h"\ + "..\..\include\mpm_common.h"\ + "..\..\include\os.h"\ + "..\..\include\scoreboard.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_dbm.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + "..\generators\mod_status.h"\ + diff --git a/modules/cache/mod_socache_dbm.dsp b/modules/cache/mod_socache_dbm.dsp new file mode 100644 index 0000000..4de39aa --- /dev/null +++ b/modules/cache/mod_socache_dbm.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="mod_socache_dbm" - 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_socache_dbm - 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_socache_dbm.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_socache_dbm.mak" CFG="mod_socache_dbm - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_dbm - 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_socache_dbm - 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_socache_dbm_EXPORTS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_dbm_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_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_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_socache_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_socache_dbm.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_socache_dbm - 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" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_dbm_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_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_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_socache_dbm.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_socache_dbm.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_socache_dbm - Win32 Release" +# Name "mod_socache_dbm - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_socache_dbm.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_socache_dbm.mak b/modules/cache/mod_socache_dbm.mak new file mode 100644 index 0000000..93453f8 --- /dev/null +++ b/modules/cache/mod_socache_dbm.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_dbm.dsp +!IF "$(CFG)" == "" +CFG=mod_socache_dbm - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_socache_dbm - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_socache_dbm - Win32 Release" && "$(CFG)" != "mod_socache_dbm - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_socache_dbm.mak" CFG="mod_socache_dbm - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_dbm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_dbm - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_dbm.obj" + -@erase "$(INTDIR)\mod_socache_dbm.res" + -@erase "$(INTDIR)\mod_socache_dbm_src.idb" + -@erase "$(INTDIR)\mod_socache_dbm_src.pdb" + -@erase "$(OUTDIR)\mod_socache_dbm.exp" + -@erase "$(OUTDIR)\mod_socache_dbm.lib" + -@erase "$(OUTDIR)\mod_socache_dbm.pdb" + -@erase "$(OUTDIR)\mod_socache_dbm.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dbm_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dbm.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dbm.pdb" /debug /out:"$(OUTDIR)\mod_socache_dbm.so" /implib:"$(OUTDIR)\mod_socache_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_dbm.obj" \ + "$(INTDIR)\mod_socache_dbm.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_socache_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_socache_dbm.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dbm.so" + if exist .\Release\mod_socache_dbm.so.manifest mt.exe -manifest .\Release\mod_socache_dbm.so.manifest -outputresource:.\Release\mod_socache_dbm.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_dbm.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_dbm.obj" + -@erase "$(INTDIR)\mod_socache_dbm.res" + -@erase "$(INTDIR)\mod_socache_dbm_src.idb" + -@erase "$(INTDIR)\mod_socache_dbm_src.pdb" + -@erase "$(OUTDIR)\mod_socache_dbm.exp" + -@erase "$(OUTDIR)\mod_socache_dbm.lib" + -@erase "$(OUTDIR)\mod_socache_dbm.pdb" + -@erase "$(OUTDIR)\mod_socache_dbm.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dbm_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dbm.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dbm.pdb" /debug /out:"$(OUTDIR)\mod_socache_dbm.so" /implib:"$(OUTDIR)\mod_socache_dbm.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dbm.so +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_dbm.obj" \ + "$(INTDIR)\mod_socache_dbm.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_socache_dbm.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_socache_dbm.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dbm.so" + if exist .\Debug\mod_socache_dbm.so.manifest mt.exe -manifest .\Debug\mod_socache_dbm.so.manifest -outputresource:.\Debug\mod_socache_dbm.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_socache_dbm.dep") +!INCLUDE "mod_socache_dbm.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_socache_dbm.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" || "$(CFG)" == "mod_socache_dbm - Win32 Debug" + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_socache_dbm - Win32 Release" + + +"$(INTDIR)\mod_socache_dbm.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_socache_dbm - Win32 Debug" + + +"$(INTDIR)\mod_socache_dbm.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dbm.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_dbm.so" /d LONG_NAME="socache_dbm_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_socache_dbm.c + +"$(INTDIR)\mod_socache_dbm.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_socache_dc.c b/modules/cache/mod_socache_dc.c new file mode 100644 index 0000000..c1d4ab8 --- /dev/null +++ b/modules/cache/mod_socache_dc.c @@ -0,0 +1,198 @@ +/* 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 "httpd.h" +#include "http_log.h" +#include "http_request.h" +#include "http_config.h" +#include "http_protocol.h" +#include "mod_status.h" + +#include "apr_strings.h" +#include "apr_time.h" + +#include "ap_socache.h" + +#include "distcache/dc_client.h" + +#if !defined(DISTCACHE_CLIENT_API) || (DISTCACHE_CLIENT_API < 0x0001) +#error "You must compile with a more recent version of the distcache-base package" +#endif + +struct ap_socache_instance_t { + /* Configured target server: */ + const char *target; + /* distcache client context: */ + DC_CTX *dc; +}; + +static const char *socache_dc_create(ap_socache_instance_t **context, + const char *arg, + apr_pool_t *tmp, apr_pool_t *p) +{ + struct ap_socache_instance_t *ctx; + + ctx = *context = apr_palloc(p, sizeof *ctx); + + ctx->target = apr_pstrdup(p, arg); + + return NULL; +} + +static apr_status_t socache_dc_init(ap_socache_instance_t *ctx, + const char *namespace, + const struct ap_socache_hints *hints, + server_rec *s, apr_pool_t *p) +{ +#if 0 + /* If a "persistent connection" mode of operation is preferred, you *must* + * also use the PIDCHECK flag to ensure fork()'d processes don't interlace + * comms on the same connection as each other. */ +#define SESSION_CTX_FLAGS SESSION_CTX_FLAG_PERSISTENT | \ + SESSION_CTX_FLAG_PERSISTENT_PIDCHECK | \ + SESSION_CTX_FLAG_PERSISTENT_RETRY | \ + SESSION_CTX_FLAG_PERSISTENT_LATE +#else + /* This mode of operation will open a temporary connection to the 'target' + * for each cache operation - this makes it safe against fork() + * automatically. This mode is preferred when running a local proxy (over + * unix domain sockets) because overhead is negligable and it reduces the + * performance/stability danger of file-descriptor bloatage. */ +#define SESSION_CTX_FLAGS 0 +#endif + ctx->dc = DC_CTX_new(ctx->target, SESSION_CTX_FLAGS); + if (!ctx->dc) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00738) "distributed scache failed to obtain context"); + return APR_EGENERAL; + } + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00739) "distributed scache context initialised"); + + return APR_SUCCESS; +} + +static void socache_dc_destroy(ap_socache_instance_t *ctx, server_rec *s) +{ + if (ctx && ctx->dc) { + DC_CTX_free(ctx->dc); + ctx->dc = NULL; + } +} + +static apr_status_t socache_dc_store(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, unsigned int idlen, + apr_time_t expiry, + unsigned char *der, unsigned int der_len, + apr_pool_t *p) +{ + /* !@#$%^ - why do we deal with *absolute* time anyway??? + * Uhm - because most things expire things at a specific time? + * Were the API were thought out expiry - r->request_time is a good approximation + */ + expiry -= apr_time_now(); + /* Send the serialised session to the distributed cache context */ + if (!DC_CTX_add_session(ctx->dc, id, idlen, der, der_len, + apr_time_msec(expiry))) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00740) "distributed scache 'store' failed"); + return APR_EGENERAL; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00741) "distributed scache 'store' successful"); + return APR_SUCCESS; +} + +static apr_status_t socache_dc_retrieve(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen, + apr_pool_t *p) +{ + unsigned int data_len; + + /* Retrieve any corresponding session from the distributed cache context */ + if (!DC_CTX_get_session(ctx->dc, id, idlen, dest, *destlen, &data_len)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00742) "distributed scache 'retrieve' MISS"); + return APR_NOTFOUND; + } + if (data_len > *destlen) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00743) "distributed scache 'retrieve' OVERFLOW"); + return APR_ENOSPC; + } + *destlen = data_len; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00744) "distributed scache 'retrieve' HIT"); + return APR_SUCCESS; +} + +static apr_status_t socache_dc_remove(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_pool_t *p) +{ + /* Remove any corresponding session from the distributed cache context */ + if (!DC_CTX_remove_session(ctx->dc, id, idlen)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00745) "distributed scache 'remove' MISS"); + return APR_NOTFOUND; + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00746) "distributed scache 'remove' HIT"); + return APR_SUCCESS; + } +} + +static void socache_dc_status(ap_socache_instance_t *ctx, request_rec *r, int flags) +{ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00747) + "distributed scache 'socache_dc_status'"); + if (!(flags & AP_STATUS_SHORT)) { + ap_rprintf(r, "cache type: <b>DC (Distributed Cache)</b>, " + " target: <b>%s</b><br>", ctx->target); + } + else { + ap_rputs("CacheType: DC\n", r); + ap_rvputs(r, "CacheTarget: ", ctx->target, "\n", NULL); + } +} + +static apr_status_t socache_dc_iterate(ap_socache_instance_t *instance, + server_rec *s, void *userctx, + ap_socache_iterator_t *iterator, + apr_pool_t *pool) +{ + return APR_ENOTIMPL; +} + +static const ap_socache_provider_t socache_dc = { + "distcache", + 0, + socache_dc_create, + socache_dc_init, + socache_dc_destroy, + socache_dc_store, + socache_dc_retrieve, + socache_dc_remove, + socache_dc_status, + socache_dc_iterate +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dc", + AP_SOCACHE_PROVIDER_VERSION, + &socache_dc); +} + +AP_DECLARE_MODULE(socache_dc) = { + STANDARD20_MODULE_STUFF, + NULL, NULL, NULL, NULL, NULL, + register_hooks +}; + diff --git a/modules/cache/mod_socache_dc.dep b/modules/cache/mod_socache_dc.dep new file mode 100644 index 0000000..ef969f7 --- /dev/null +++ b/modules/cache/mod_socache_dc.dep @@ -0,0 +1,55 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_socache_dc.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_socache_dc.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\ap_socache.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + diff --git a/modules/cache/mod_socache_dc.dsp b/modules/cache/mod_socache_dc.dsp new file mode 100644 index 0000000..0ff315c --- /dev/null +++ b/modules/cache/mod_socache_dc.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="mod_socache_dc" - 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_socache_dc - 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_socache_dc.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_socache_dc.mak" CFG="mod_socache_dc - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_dc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_dc - 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_socache_dc - 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_socache_dc_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_socache_dc_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_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_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_socache_dc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_socache_dc.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_socache_dc - 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_socache_dc_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_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_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_socache_dc.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_socache_dc.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_socache_dc - Win32 Release" +# Name "mod_socache_dc - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_socache_dc.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_socache_dc.mak b/modules/cache/mod_socache_dc.mak new file mode 100644 index 0000000..0c4c3c0 --- /dev/null +++ b/modules/cache/mod_socache_dc.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_dc.dsp +!IF "$(CFG)" == "" +CFG=mod_socache_dc - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_socache_dc - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_socache_dc - Win32 Release" && "$(CFG)" != "mod_socache_dc - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_socache_dc.mak" CFG="mod_socache_dc - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_dc - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_dc - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_dc.obj" + -@erase "$(INTDIR)\mod_socache_dc.res" + -@erase "$(INTDIR)\mod_socache_dc_src.idb" + -@erase "$(INTDIR)\mod_socache_dc_src.pdb" + -@erase "$(OUTDIR)\mod_socache_dc.exp" + -@erase "$(OUTDIR)\mod_socache_dc.lib" + -@erase "$(OUTDIR)\mod_socache_dc.pdb" + -@erase "$(OUTDIR)\mod_socache_dc.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dc_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dc.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dc.pdb" /debug /out:"$(OUTDIR)\mod_socache_dc.so" /implib:"$(OUTDIR)\mod_socache_dc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_dc.obj" \ + "$(INTDIR)\mod_socache_dc.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_socache_dc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_socache_dc.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dc.so" + if exist .\Release\mod_socache_dc.so.manifest mt.exe -manifest .\Release\mod_socache_dc.so.manifest -outputresource:.\Release\mod_socache_dc.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_dc.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_dc.obj" + -@erase "$(INTDIR)\mod_socache_dc.res" + -@erase "$(INTDIR)\mod_socache_dc_src.idb" + -@erase "$(INTDIR)\mod_socache_dc_src.pdb" + -@erase "$(OUTDIR)\mod_socache_dc.exp" + -@erase "$(OUTDIR)\mod_socache_dc.lib" + -@erase "$(OUTDIR)\mod_socache_dc.pdb" + -@erase "$(OUTDIR)\mod_socache_dc.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_dc_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_dc.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_dc.pdb" /debug /out:"$(OUTDIR)\mod_socache_dc.so" /implib:"$(OUTDIR)\mod_socache_dc.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_dc.so +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_dc.obj" \ + "$(INTDIR)\mod_socache_dc.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_socache_dc.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_socache_dc.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_dc.so" + if exist .\Debug\mod_socache_dc.so.manifest mt.exe -manifest .\Debug\mod_socache_dc.so.manifest -outputresource:.\Debug\mod_socache_dc.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_socache_dc.dep") +!INCLUDE "mod_socache_dc.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_socache_dc.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" || "$(CFG)" == "mod_socache_dc - Win32 Debug" + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_socache_dc - Win32 Release" + + +"$(INTDIR)\mod_socache_dc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_socache_dc - Win32 Debug" + + +"$(INTDIR)\mod_socache_dc.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_dc.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_dc.so" /d LONG_NAME="socache_dc_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_socache_dc.c + +"$(INTDIR)\mod_socache_dc.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_socache_memcache.c b/modules/cache/mod_socache_memcache.c new file mode 100644 index 0000000..e943b9b --- /dev/null +++ b/modules/cache/mod_socache_memcache.c @@ -0,0 +1,431 @@ +/* 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 "httpd.h" +#include "http_config.h" +#include "http_protocol.h" + +#include "apr.h" +#include "apu_version.h" + +/* apr_memcache support requires >= 1.3 */ +#if APU_MAJOR_VERSION > 1 || \ + (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 2) +#define HAVE_APU_MEMCACHE 1 +#endif + +#ifdef HAVE_APU_MEMCACHE + +#include "ap_socache.h" +#include "ap_mpm.h" +#include "http_log.h" +#include "apr_memcache.h" +#include "apr_strings.h" +#include "mod_status.h" + +/* The underlying apr_memcache system is thread safe.. */ +#define MC_KEY_LEN 254 + +#ifndef MC_DEFAULT_SERVER_PORT +#define MC_DEFAULT_SERVER_PORT 11211 +#endif + + +#ifndef MC_DEFAULT_SERVER_MIN +#define MC_DEFAULT_SERVER_MIN 0 +#endif + +#ifndef MC_DEFAULT_SERVER_SMAX +#define MC_DEFAULT_SERVER_SMAX 1 +#endif + +#ifndef MC_DEFAULT_SERVER_TTL +#define MC_DEFAULT_SERVER_TTL apr_time_from_sec(15) +#endif + +module AP_MODULE_DECLARE_DATA socache_memcache_module; + +typedef struct { + apr_uint32_t ttl; +} socache_mc_svr_cfg; + +struct ap_socache_instance_t { + const char *servers; + apr_memcache_t *mc; + const char *tag; + apr_size_t taglen; /* strlen(tag) + 1 */ +}; + +static const char *socache_mc_create(ap_socache_instance_t **context, + const char *arg, + apr_pool_t *tmp, apr_pool_t *p) +{ + ap_socache_instance_t *ctx; + + *context = ctx = apr_palloc(p, sizeof *ctx); + + if (!arg || !*arg) { + return "List of server names required to create memcache socache."; + } + + ctx->servers = apr_pstrdup(p, arg); + + return NULL; +} + +static apr_status_t socache_mc_init(ap_socache_instance_t *ctx, + const char *namespace, + const struct ap_socache_hints *hints, + server_rec *s, apr_pool_t *p) +{ + apr_status_t rv; + int thread_limit = 0; + apr_uint16_t nservers = 0; + char *cache_config; + char *split; + char *tok; + + socache_mc_svr_cfg *sconf = ap_get_module_config(s->module_config, + &socache_memcache_module); + + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + + /* Find all the servers in the first run to get a total count */ + cache_config = apr_pstrdup(p, ctx->servers); + split = apr_strtok(cache_config, ",", &tok); + while (split) { + nservers++; + split = apr_strtok(NULL,",", &tok); + } + + rv = apr_memcache_create(p, nservers, 0, &ctx->mc); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00785) + "Failed to create Memcache Object of '%d' size.", + nservers); + return rv; + } + + /* Now add each server to the memcache */ + cache_config = apr_pstrdup(p, ctx->servers); + split = apr_strtok(cache_config, ",", &tok); + while (split) { + apr_memcache_server_t *st; + char *host_str; + char *scope_id; + apr_port_t port; + + rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00786) + "Failed to Parse memcache Server: '%s'", split); + return rv; + } + + if (host_str == NULL) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00787) + "Failed to Parse Server, " + "no hostname specified: '%s'", split); + return APR_EINVAL; + } + + if (port == 0) { + port = MC_DEFAULT_SERVER_PORT; + } + + rv = apr_memcache_server_create(p, + host_str, port, + MC_DEFAULT_SERVER_MIN, + MC_DEFAULT_SERVER_SMAX, + thread_limit, + sconf->ttl, + &st); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00788) + "Failed to Create memcache Server: %s:%d", + host_str, port); + return rv; + } + + rv = apr_memcache_add_server(ctx->mc, st); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00789) + "Failed to Add memcache Server: %s:%d", + host_str, port); + return rv; + } + + split = apr_strtok(NULL,",", &tok); + } + + ctx->tag = apr_pstrcat(p, namespace, ":", NULL); + ctx->taglen = strlen(ctx->tag) + 1; + + /* socache API constraint: */ + AP_DEBUG_ASSERT(ctx->taglen <= 16); + + return APR_SUCCESS; +} + +static void socache_mc_destroy(ap_socache_instance_t *context, server_rec *s) +{ + /* noop. */ +} + +/* Converts (binary) id into a key prefixed by the predetermined + * namespace tag; writes output to key buffer. Returns non-zero if + * the id won't fit in the key buffer. */ +static int socache_mc_id2key(ap_socache_instance_t *ctx, + const unsigned char *id, unsigned int idlen, + char *key, apr_size_t keylen) +{ + char *cp; + + if (idlen * 2 + ctx->taglen >= keylen) + return 1; + + cp = apr_cpystrn(key, ctx->tag, ctx->taglen); + ap_bin2hex(id, idlen, cp); + + return 0; +} + +static apr_status_t socache_mc_store(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, unsigned int idlen, + apr_time_t expiry, + unsigned char *ucaData, unsigned int nData, + apr_pool_t *p) +{ + char buf[MC_KEY_LEN]; + apr_status_t rv; + + if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { + return APR_EINVAL; + } + + /* memcache needs time in seconds till expiry; fail if this is not + * positive *before* casting to unsigned (apr_uint32_t). */ + expiry -= apr_time_now(); + if (apr_time_sec(expiry) <= 0) { + return APR_EINVAL; + } + rv = apr_memcache_set(ctx->mc, buf, (char*)ucaData, nData, + apr_time_sec(expiry), 0); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00790) + "scache_mc: error setting key '%s' " + "with %d bytes of data", buf, nData); + return rv; + } + + return APR_SUCCESS; +} + +static apr_status_t socache_mc_retrieve(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen, + apr_pool_t *p) +{ + apr_size_t data_len; + char buf[MC_KEY_LEN], *data; + apr_status_t rv; + + if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { + return APR_EINVAL; + } + + /* ### this could do with a subpool, but _getp looks like it will + * eat memory like it's going out of fashion anyway. */ + + rv = apr_memcache_getp(ctx->mc, p, buf, &data, &data_len, NULL); + if (rv) { + if (rv != APR_NOTFOUND) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00791) + "scache_mc: 'retrieve' FAIL"); + } + return rv; + } + else if (data_len > *destlen) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00792) + "scache_mc: 'retrieve' OVERFLOW"); + return APR_ENOMEM; + } + + memcpy(dest, data, data_len); + *destlen = data_len; + + return APR_SUCCESS; +} + +static apr_status_t socache_mc_remove(ap_socache_instance_t *ctx, server_rec *s, + const unsigned char *id, + unsigned int idlen, apr_pool_t *p) +{ + char buf[MC_KEY_LEN]; + apr_status_t rv; + + if (socache_mc_id2key(ctx, id, idlen, buf, sizeof buf)) { + return APR_EINVAL; + } + + rv = apr_memcache_delete(ctx->mc, buf, 0); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00793) + "scache_mc: error deleting key '%s' ", + buf); + } + + return rv; +} + +static void socache_mc_status(ap_socache_instance_t *ctx, request_rec *r, int flags) +{ + apr_memcache_t *rc = ctx->mc; + int i; + + for (i = 0; i < rc->ntotal; i++) { + apr_memcache_server_t *ms; + apr_memcache_stats_t *stats; + apr_status_t rv; + char *br = (!(flags & AP_STATUS_SHORT) ? "<br />" : ""); + + ms = rc->live_servers[i]; + + ap_rprintf(r, "Memcached server: %s:%d [%s]%s\n", ms->host, (int)ms->port, + (ms->status == APR_MC_SERVER_LIVE) ? "Up" : "Down", + br); + rv = apr_memcache_stats(ms, r->pool, &stats); + if (rv != APR_SUCCESS) + continue; + if (!(flags & AP_STATUS_SHORT)) { + ap_rprintf(r, "<b>Version:</b> <i>%s</i> [%u bits], PID: <i>%u</i>, Uptime: <i>%u hrs</i> <br />\n", + stats->version , stats->pointer_size, stats->pid, stats->uptime/3600); + ap_rprintf(r, "<b>Clients::</b> Structures: <i>%u</i>, Total: <i>%u</i>, Current: <i>%u</i> <br />\n", + stats->connection_structures, stats->total_connections, stats->curr_connections); + ap_rprintf(r, "<b>Storage::</b> Total Items: <i>%u</i>, Current Items: <i>%u</i>, Bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n", + stats->total_items, stats->curr_items, stats->bytes); + ap_rprintf(r, "<b>CPU::</b> System: <i>%u</i>, User: <i>%u</i> <br />\n", + (unsigned)stats->rusage_system, (unsigned)stats->rusage_user ); + ap_rprintf(r, "<b>Cache::</b> Gets: <i>%u</i>, Sets: <i>%u</i>, Hits: <i>%u</i>, Misses: <i>%u</i> <br />\n", + stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses); + ap_rprintf(r, "<b>Net::</b> Input bytes: <i>%" APR_UINT64_T_FMT "</i>, Output bytes: <i>%" APR_UINT64_T_FMT "</i> <br />\n", + stats->bytes_read, stats->bytes_written); + ap_rprintf(r, "<b>Misc::</b> Evictions: <i>%" APR_UINT64_T_FMT "</i>, MaxMem: <i>%u</i>, Threads: <i>%u</i> <br />\n", + stats->evictions, stats->limit_maxbytes, stats->threads); + ap_rputs("<hr><br />\n", r); + } + else { + ap_rprintf(r, "Version: %s [%u bits], PID: %u, Uptime: %u hrs %s\n", + stats->version , stats->pointer_size, stats->pid, stats->uptime/3600, br); + ap_rprintf(r, "Clients:: Structures: %d, Total: %d, Current: %u %s\n", + stats->connection_structures, stats->total_connections, stats->curr_connections, br); + ap_rprintf(r, "Storage:: Total Items: %u, Current Items: %u, Bytes: %" APR_UINT64_T_FMT " %s\n", + stats->total_items, stats->curr_items, stats->bytes, br); + ap_rprintf(r, "CPU:: System: %u, User: %u %s\n", + (unsigned)stats->rusage_system, (unsigned)stats->rusage_user , br); + ap_rprintf(r, "Cache:: Gets: %u, Sets: %u, Hits: %u, Misses: %u %s\n", + stats->cmd_get, stats->cmd_set, stats->get_hits, stats->get_misses, br); + ap_rprintf(r, "Net:: Input bytes: %" APR_UINT64_T_FMT ", Output bytes: %" APR_UINT64_T_FMT " %s\n", + stats->bytes_read, stats->bytes_written, br); + ap_rprintf(r, "Misc:: Evictions: %" APR_UINT64_T_FMT ", MaxMem: %u, Threads: %u %s\n", + stats->evictions, stats->limit_maxbytes, stats->threads, br); + } + } + +} + +static apr_status_t socache_mc_iterate(ap_socache_instance_t *instance, + server_rec *s, void *userctx, + ap_socache_iterator_t *iterator, + apr_pool_t *pool) +{ + return APR_ENOTIMPL; +} + +static const ap_socache_provider_t socache_mc = { + "memcache", + 0, + socache_mc_create, + socache_mc_init, + socache_mc_destroy, + socache_mc_store, + socache_mc_retrieve, + socache_mc_remove, + socache_mc_status, + socache_mc_iterate +}; + +#endif /* HAVE_APU_MEMCACHE */ + +static void *create_server_config(apr_pool_t *p, server_rec *s) +{ + socache_mc_svr_cfg *sconf = apr_pcalloc(p, sizeof(socache_mc_svr_cfg)); + + sconf->ttl = MC_DEFAULT_SERVER_TTL; + + return sconf; +} + +static const char *socache_mc_set_ttl(cmd_parms *cmd, void *dummy, + const char *arg) +{ + apr_interval_time_t ttl; + socache_mc_svr_cfg *sconf = ap_get_module_config(cmd->server->module_config, + &socache_memcache_module); + + if (ap_timeout_parameter_parse(arg, &ttl, "s") != APR_SUCCESS) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + " has wrong format", NULL); + } + + if ((ttl < apr_time_from_sec(0)) || (ttl > apr_time_from_sec(3600))) { + return apr_pstrcat(cmd->pool, cmd->cmd->name, + " can only be 0 or up to one hour.", NULL); + } + + /* apr_memcache_server_create needs a ttl in usec. */ + sconf->ttl = ttl; + + return NULL; +} + +static void register_hooks(apr_pool_t *p) +{ +#ifdef HAVE_APU_MEMCACHE + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "memcache", + AP_SOCACHE_PROVIDER_VERSION, + &socache_mc); +#endif +} + +static const command_rec socache_memcache_cmds[] = { + AP_INIT_TAKE1("MemcacheConnTTL", socache_mc_set_ttl, NULL, RSRC_CONF, + "TTL used for the connection with the memcache server(s)"), + { NULL } +}; + +AP_DECLARE_MODULE(socache_memcache) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + create_server_config, /* create per-server config structures */ + NULL, /* merge per-server config structures */ + socache_memcache_cmds, /* table of config file commands */ + register_hooks /* register hooks */ +}; diff --git a/modules/cache/mod_socache_memcache.dep b/modules/cache/mod_socache_memcache.dep new file mode 100644 index 0000000..2929c5e --- /dev/null +++ b/modules/cache/mod_socache_memcache.dep @@ -0,0 +1,59 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_socache_memcache.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_socache_memcache.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_mpm.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\ap_socache.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_log.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\scoreboard.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_memcache.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_reslist.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr-util\include\apu_version.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_hash.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_version.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + diff --git a/modules/cache/mod_socache_memcache.dsp b/modules/cache/mod_socache_memcache.dsp new file mode 100644 index 0000000..d530826 --- /dev/null +++ b/modules/cache/mod_socache_memcache.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="mod_socache_memcache" - 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_socache_memcache - 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_socache_memcache.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_socache_memcache.mak" CFG="mod_socache_memcache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_memcache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_memcache - 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_socache_memcache - 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_socache_memcache_EXPORTS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_memcache_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_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_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_socache_memcache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_socache_memcache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_socache_memcache - 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" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_memcache_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_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_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_socache_memcache.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_socache_memcache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_socache_memcache - Win32 Release" +# Name "mod_socache_memcache - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_socache_memcache.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_socache_memcache.mak b/modules/cache/mod_socache_memcache.mak new file mode 100644 index 0000000..b75a9a1 --- /dev/null +++ b/modules/cache/mod_socache_memcache.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_memcache.dsp +!IF "$(CFG)" == "" +CFG=mod_socache_memcache - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_socache_memcache - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_socache_memcache - Win32 Release" && "$(CFG)" != "mod_socache_memcache - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_socache_memcache.mak" CFG="mod_socache_memcache - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_memcache - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_memcache - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_memcache.obj" + -@erase "$(INTDIR)\mod_socache_memcache.res" + -@erase "$(INTDIR)\mod_socache_memcache_src.idb" + -@erase "$(INTDIR)\mod_socache_memcache_src.pdb" + -@erase "$(OUTDIR)\mod_socache_memcache.exp" + -@erase "$(OUTDIR)\mod_socache_memcache.lib" + -@erase "$(OUTDIR)\mod_socache_memcache.pdb" + -@erase "$(OUTDIR)\mod_socache_memcache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_memcache_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_memcache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_memcache.pdb" /debug /out:"$(OUTDIR)\mod_socache_memcache.so" /implib:"$(OUTDIR)\mod_socache_memcache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_memcache.obj" \ + "$(INTDIR)\mod_socache_memcache.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_socache_memcache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_socache_memcache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_memcache.so" + if exist .\Release\mod_socache_memcache.so.manifest mt.exe -manifest .\Release\mod_socache_memcache.so.manifest -outputresource:.\Release\mod_socache_memcache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_memcache.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_memcache.obj" + -@erase "$(INTDIR)\mod_socache_memcache.res" + -@erase "$(INTDIR)\mod_socache_memcache_src.idb" + -@erase "$(INTDIR)\mod_socache_memcache_src.pdb" + -@erase "$(OUTDIR)\mod_socache_memcache.exp" + -@erase "$(OUTDIR)\mod_socache_memcache.lib" + -@erase "$(OUTDIR)\mod_socache_memcache.pdb" + -@erase "$(OUTDIR)\mod_socache_memcache.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_memcache_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_memcache.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_memcache.pdb" /debug /out:"$(OUTDIR)\mod_socache_memcache.so" /implib:"$(OUTDIR)\mod_socache_memcache.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_memcache.so +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_memcache.obj" \ + "$(INTDIR)\mod_socache_memcache.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_socache_memcache.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_socache_memcache.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_memcache.so" + if exist .\Debug\mod_socache_memcache.so.manifest mt.exe -manifest .\Debug\mod_socache_memcache.so.manifest -outputresource:.\Debug\mod_socache_memcache.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_socache_memcache.dep") +!INCLUDE "mod_socache_memcache.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_socache_memcache.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" || "$(CFG)" == "mod_socache_memcache - Win32 Debug" + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_socache_memcache - Win32 Release" + + +"$(INTDIR)\mod_socache_memcache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_socache_memcache - Win32 Debug" + + +"$(INTDIR)\mod_socache_memcache.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_memcache.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_memcache.so" /d LONG_NAME="socache_memcache_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_socache_memcache.c + +"$(INTDIR)\mod_socache_memcache.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + diff --git a/modules/cache/mod_socache_shmcb.c b/modules/cache/mod_socache_shmcb.c new file mode 100644 index 0000000..2750f25 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.c @@ -0,0 +1,1072 @@ +/* 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 "httpd.h" +#include "http_log.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_config.h" +#include "mod_status.h" + +#include "apr.h" +#include "apr_strings.h" +#include "apr_time.h" +#include "apr_shm.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_general.h" + +#if APR_HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "ap_socache.h" + +/* XXX Unfortunately, there are still many unsigned ints in use here, so we + * XXX cannot allow more than UINT_MAX. Since some of the ints are exposed in + * XXX public interfaces, a simple search and replace is not enough. + * XXX It should be possible to extend that so that the total cache size can + * XXX be APR_SIZE_MAX and only the object size needs to be smaller than + * XXX UINT_MAX. + */ +#define SHMCB_MAX_SIZE (UINT_MAX<APR_SIZE_MAX ? UINT_MAX : APR_SIZE_MAX) + +#define DEFAULT_SHMCB_PREFIX "socache-shmcb-" + +#define DEFAULT_SHMCB_SUFFIX ".cache" + +#define ALIGNED_HEADER_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBHeader)) +#define ALIGNED_SUBCACHE_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBSubcache)) +#define ALIGNED_INDEX_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBIndex)) + +/* + * Header structure - the start of the shared-mem segment + */ +typedef struct { + /* Stats for cache operations */ + unsigned long stat_stores; + unsigned long stat_replaced; + unsigned long stat_expiries; + unsigned long stat_scrolled; + unsigned long stat_retrieves_hit; + unsigned long stat_retrieves_miss; + unsigned long stat_removes_hit; + unsigned long stat_removes_miss; + /* Number of subcaches */ + unsigned int subcache_num; + /* How many indexes each subcache's queue has */ + unsigned int index_num; + /* How large each subcache is, including the queue and data */ + unsigned int subcache_size; + /* How far into each subcache the data area is (optimisation) */ + unsigned int subcache_data_offset; + /* How large the data area in each subcache is (optimisation) */ + unsigned int subcache_data_size; +} SHMCBHeader; + +/* + * Subcache structure - the start of each subcache, followed by + * indexes then data + */ +typedef struct { + /* The start position and length of the cyclic buffer of indexes */ + unsigned int idx_pos, idx_used; + /* Same for the data area */ + unsigned int data_pos, data_used; +} SHMCBSubcache; + +/* + * Index structure - each subcache has an array of these + */ +typedef struct { + /* absolute time this entry expires */ + apr_time_t expires; + /* location within the subcache's data area */ + unsigned int data_pos; + /* size (most logic ignores this, we keep it only to minimise memcpy) */ + unsigned int data_used; + /* length of the used data which contains the id */ + unsigned int id_len; + /* Used to mark explicitly-removed socache entries */ + unsigned char removed; +} SHMCBIndex; + +struct ap_socache_instance_t { + const char *data_file; + apr_size_t shm_size; + apr_shm_t *shm; + SHMCBHeader *header; +}; + +/* The SHM data segment is of fixed size and stores data as follows. + * + * [ SHMCBHeader | Subcaches ] + * + * The SHMCBHeader header structure stores metadata concerning the + * cache and the contained subcaches. + * + * Subcaches is a hash table of header->subcache_num SHMCBSubcache + * structures. The hash table is indexed by SHMCB_MASK(id). Each + * SHMCBSubcache structure has a fixed size (header->subcache_size), + * which is determined at creation time, and looks like the following: + * + * [ SHMCBSubcache | Indexes | Data ] + * + * Each subcache is prefixed by the SHMCBSubcache structure. + * + * The subcache's "Data" segment is a single cyclic data buffer, of + * total size header->subcache_data_size; data inside is referenced + * using byte offsets. The offset marking the beginning of the cyclic + * buffer is subcache->data_pos; the buffer's length is + * subcache->data_used. + * + * "Indexes" is an array of header->index_num SHMCBIndex structures, + * which is used as a cyclic queue; subcache->idx_pos gives the array + * index of the first in use, subcache->idx_used gives the number in + * use. Both ->idx_* values have a range of [0, header->index_num) + * + * Each in-use SHMCBIndex structure represents a single cached object. + * The ID and data segment are stored consecutively in the subcache's + * cyclic data buffer. The "Data" segment can thus be seen to + * look like this, for example + * + * offset: [ 0 1 2 3 4 5 6 ... + * contents:[ ID1 Data1 ID2 Data2 ID3 ... + * + * where the corresponding indices would look like: + * + * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...} + * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...} + * ... + */ + +/* This macro takes a pointer to the header and a zero-based index and returns + * a pointer to the corresponding subcache. */ +#define SHMCB_SUBCACHE(pHeader, num) \ + (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \ + ALIGNED_HEADER_SIZE + \ + (num) * ((pHeader)->subcache_size)) + +/* This macro takes a pointer to the header and an id and returns a + * pointer to the corresponding subcache. */ +#define SHMCB_MASK(pHeader, id) \ + SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1)) + +/* This macro takes the same params as the last, generating two outputs for use + * in ap_log_error(...). */ +#define SHMCB_MASK_DBG(pHeader, id) \ + *(id), (*(id) & ((pHeader)->subcache_num - 1)) + +/* This macro takes a pointer to a subcache and a zero-based index and returns + * a pointer to the corresponding SHMCBIndex. */ +#define SHMCB_INDEX(pSubcache, num) \ + (SHMCBIndex *)(((unsigned char *)pSubcache) + \ + ALIGNED_SUBCACHE_SIZE + \ + (num) * ALIGNED_INDEX_SIZE) + +/* This macro takes a pointer to the header and a subcache and returns a + * pointer to the corresponding data area. */ +#define SHMCB_DATA(pHeader, pSubcache) \ + ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset) + +/* + * Cyclic functions - assists in "wrap-around"/modulo logic + */ + +/* Addition modulo 'mod' */ +#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \ + (((val) + (inc)) % (mod)) + +/* Subtraction (or "distance between") modulo 'mod' */ +#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \ + ((val2) >= (val1) ? ((val2) - (val1)) : \ + ((val2) + (mod) - (val1))) + +/* A "normal-to-cyclic" memcpy. */ +static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data, + unsigned int dest_offset, const unsigned char *src, + unsigned int src_len) +{ + if (dest_offset + src_len < buf_size) + /* It be copied all in one go */ + memcpy(data + dest_offset, src, src_len); + else { + /* Copy the two splits */ + memcpy(data + dest_offset, src, buf_size - dest_offset); + memcpy(data, src + buf_size - dest_offset, + src_len + dest_offset - buf_size); + } +} + +/* A "cyclic-to-normal" memcpy. */ +static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest, + const unsigned char *data, unsigned int src_offset, + unsigned int src_len) +{ + if (src_offset + src_len < buf_size) + /* It be copied all in one go */ + memcpy(dest, data + src_offset, src_len); + else { + /* Copy the two splits */ + memcpy(dest, data + src_offset, buf_size - src_offset); + memcpy(dest + buf_size - src_offset, data, + src_len + src_offset - buf_size); + } +} + +/* A memcmp against a cyclic data buffer. Compares SRC of length + * SRC_LEN against the contents of cyclic buffer DATA (which is of + * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */ +static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data, + unsigned int dest_offset, + const unsigned char *src, + unsigned int src_len) +{ + if (dest_offset + src_len < buf_size) + /* It be compared all in one go */ + return memcmp(data + dest_offset, src, src_len); + else { + /* Compare the two splits */ + int diff; + + diff = memcmp(data + dest_offset, src, buf_size - dest_offset); + if (diff) { + return diff; + } + return memcmp(data, src + buf_size - dest_offset, + src_len + dest_offset - buf_size); + } +} + + +/* Prototypes for low-level subcache operations */ +static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *, + apr_time_t); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + unsigned char *data, unsigned int data_len, + const unsigned char *id, unsigned int id_len, + apr_time_t expiry); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *, + const unsigned char *id, unsigned int idlen, + unsigned char *data, unsigned int *datalen); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *, + const unsigned char *, unsigned int); + +/* Returns result of the (iterator)() call, zero is success (continue) */ +static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, + server_rec *s, + void *userctx, + SHMCBHeader *header, + SHMCBSubcache *subcache, + ap_socache_iterator_t *iterator, + unsigned char **buf, + apr_size_t *buf_len, + apr_pool_t *pool, + apr_time_t now); + +/* + * High-Level "handlers" as per ssl_scache.c + * subcache internals are deferred to shmcb_subcache_*** functions lower down + */ + +static const char *socache_shmcb_create(ap_socache_instance_t **context, + const char *arg, + apr_pool_t *tmp, apr_pool_t *p) +{ + ap_socache_instance_t *ctx; + char *path, *cp, *cp2; + + /* Allocate the context. */ + *context = ctx = apr_pcalloc(p, sizeof *ctx); + + ctx->shm_size = 1024*512; /* 512KB */ + + if (!arg || *arg == '\0') { + /* Use defaults. */ + return NULL; + } + + ctx->data_file = path = ap_server_root_relative(p, arg); + + cp = strrchr(path, '('); + cp2 = path + strlen(path) - 1; + if (cp) { + char *endptr; + if (*cp2 != ')') { + return "Invalid argument: no closing parenthesis or cache size " + "missing after pathname with parenthesis"; + } + *cp++ = '\0'; + *cp2 = '\0'; + + + ctx->shm_size = strtol(cp, &endptr, 10); + if (endptr != cp2) { + return "Invalid argument: cache size not numerical"; + } + + if (ctx->shm_size < 8192) { + return "Invalid argument: size has to be >= 8192 bytes"; + + } + + if (ctx->shm_size >= SHMCB_MAX_SIZE) { + return apr_psprintf(tmp, "Invalid argument: size has " + "to be < %" APR_SIZE_T_FMT " bytes on this platform", + SHMCB_MAX_SIZE); + } + } + else if (cp2 >= path && *cp2 == ')') { + return "Invalid argument: no opening parenthesis"; + } + + return NULL; +} + +static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx, + const char *namespace, + const struct ap_socache_hints *hints, + server_rec *s, apr_pool_t *p) +{ + void *shm_segment; + apr_size_t shm_segsize; + apr_status_t rv; + SHMCBHeader *header; + unsigned int num_subcache, num_idx, loop; + apr_size_t avg_obj_size, avg_id_len; + + /* Create shared memory segment */ + if (ctx->data_file == NULL) { + const char *path = apr_pstrcat(p, DEFAULT_SHMCB_PREFIX, namespace, + DEFAULT_SHMCB_SUFFIX, NULL); + + ctx->data_file = ap_runtime_dir_relative(p, path); + } + + /* Use anonymous shm by default, fall back on name-based. */ + rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p); + if (APR_STATUS_IS_ENOTIMPL(rv)) { + /* If anon shm isn't supported, fail if no named file was + * configured successfully; the ap_server_root_relative call + * above will return NULL for invalid paths. */ + if (ctx->data_file == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818) + "Could not use default path '%s' for shmcb socache", + ctx->data_file); + return APR_EINVAL; + } + + /* For a name-based segment, remove it first in case of a + * previous unclean shutdown. */ + apr_shm_remove(ctx->data_file, p); + + rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p); + } + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819) + "Could not allocate shared memory segment for shmcb " + "socache"); + return rv; + } + + shm_segment = apr_shm_baseaddr_get(ctx->shm); + shm_segsize = apr_shm_size_get(ctx->shm); + if (shm_segsize < (5 * ALIGNED_HEADER_SIZE)) { + /* the segment is ridiculously small, bail out */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00820) + "shared memory segment too small"); + return APR_ENOSPC; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00821) + "shmcb_init allocated %" APR_SIZE_T_FMT + " bytes of shared memory", + shm_segsize); + /* Discount the header */ + shm_segsize -= ALIGNED_HEADER_SIZE; + /* Select index size based on average object size hints, if given. */ + avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150; + avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30; + num_idx = (shm_segsize) / (avg_obj_size + avg_id_len); + num_subcache = 256; + while ((num_idx / num_subcache) < (2 * num_subcache)) + num_subcache /= 2; + num_idx /= num_subcache; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00822) + "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT + " including header), recommending %u subcaches, " + "%u indexes each", shm_segsize, + shm_segsize + ALIGNED_HEADER_SIZE, + num_subcache, num_idx); + if (num_idx < 5) { + /* we're still too small, bail out */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00823) + "shared memory segment too small"); + return APR_ENOSPC; + } + /* OK, we're sorted */ + ctx->header = header = shm_segment; + header->stat_stores = 0; + header->stat_replaced = 0; + header->stat_expiries = 0; + header->stat_scrolled = 0; + header->stat_retrieves_hit = 0; + header->stat_retrieves_miss = 0; + header->stat_removes_hit = 0; + header->stat_removes_miss = 0; + header->subcache_num = num_subcache; + /* Convert the subcache size (in bytes) to a value that is suitable for + * structure alignment on the host platform, by rounding down if necessary. */ + header->subcache_size = (size_t)(shm_segsize / num_subcache); + if (header->subcache_size != APR_ALIGN_DEFAULT(header->subcache_size)) { + header->subcache_size = APR_ALIGN_DEFAULT(header->subcache_size) - + APR_ALIGN_DEFAULT(1); + } + header->subcache_data_offset = ALIGNED_SUBCACHE_SIZE + + num_idx * ALIGNED_INDEX_SIZE; + header->subcache_data_size = header->subcache_size - + header->subcache_data_offset; + header->index_num = num_idx; + + /* Output trace info */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00824) + "shmcb_init_memory choices follow"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00825) + "subcache_num = %u", header->subcache_num); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00826) + "subcache_size = %u", header->subcache_size); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00827) + "subcache_data_offset = %u", header->subcache_data_offset); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00828) + "subcache_data_size = %u", header->subcache_data_size); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00829) + "index_num = %u", header->index_num); + /* The header is done, make the caches empty */ + for (loop = 0; loop < header->subcache_num; loop++) { + SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); + subcache->idx_pos = subcache->idx_used = 0; + subcache->data_pos = subcache->data_used = 0; + } + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00830) + "Shared memory socache initialised"); + /* Success ... */ + + return APR_SUCCESS; +} + +static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s) +{ + if (ctx && ctx->shm) { + apr_shm_destroy(ctx->shm); + ctx->shm = NULL; + } +} + +static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_time_t expiry, + unsigned char *encoded, + unsigned int len_encoded, + apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + int tryreplace; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00831) + "socache_shmcb_store (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + /* XXX: Says who? Why shouldn't this be acceptable, or padded if not? */ + if (idlen < 4) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00832) "unusably short id provided " + "(%u bytes)", idlen); + return APR_EINVAL; + } + tryreplace = shmcb_subcache_remove(s, header, subcache, id, idlen); + if (shmcb_subcache_store(s, header, subcache, encoded, + len_encoded, id, idlen, expiry)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00833) + "can't store an socache entry!"); + return APR_ENOSPC; + } + if (tryreplace == 0) { + header->stat_replaced++; + } + else { + header->stat_stores++; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00834) + "leaving socache_shmcb_store successfully"); + return APR_SUCCESS; +} + +static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx, + server_rec *s, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen, + apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + int rv; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00835) + "socache_shmcb_retrieve (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + + /* Get the entry corresponding to the id, if it exists. */ + rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen, + dest, destlen); + if (rv == 0) + header->stat_retrieves_hit++; + else + header->stat_retrieves_miss++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00836) + "leaving socache_shmcb_retrieve successfully"); + + return rv == 0 ? APR_SUCCESS : APR_NOTFOUND; +} + +static apr_status_t socache_shmcb_remove(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + apr_status_t rv; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00837) + "socache_shmcb_remove (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + if (idlen < 4) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00838) "unusably short id provided " + "(%u bytes)", idlen); + return APR_EINVAL; + } + if (shmcb_subcache_remove(s, header, subcache, id, idlen) == 0) { + header->stat_removes_hit++; + rv = APR_SUCCESS; + } else { + header->stat_removes_miss++; + rv = APR_NOTFOUND; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00839) + "leaving socache_shmcb_remove successfully"); + + return rv; +} + +static void socache_shmcb_status(ap_socache_instance_t *ctx, + request_rec *r, int flags) +{ + server_rec *s = r->server; + SHMCBHeader *header = ctx->header; + unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0; + apr_time_t idx_expiry, min_expiry = 0, max_expiry = 0; + apr_time_t now = apr_time_now(); + double expiry_total = 0; + int index_pct, cache_pct; + + AP_DEBUG_ASSERT(header->subcache_num > 0); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00840) "inside shmcb_status"); + /* Perform the iteration inside the mutex to avoid corruption or invalid + * pointer arithmetic. The rest of our logic uses read-only header data so + * doesn't need the lock. */ + /* Iterate over the subcaches */ + for (loop = 0; loop < header->subcache_num; loop++) { + SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); + shmcb_subcache_expire(s, header, subcache, now); + total += subcache->idx_used; + cache_total += subcache->data_used; + if (subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos); + non_empty_subcaches++; + idx_expiry = idx->expires; + expiry_total += (double)idx_expiry; + max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry); + if (!min_expiry) + min_expiry = idx_expiry; + else + min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry); + } + } + index_pct = (100 * total) / (header->index_num * + header->subcache_num); + cache_pct = (100 * cache_total) / (header->subcache_data_size * + header->subcache_num); + /* Generate Output */ + if (!(flags & AP_STATUS_SHORT)) { + ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> " + "bytes, current entries: <b>%d</b><br>", + ctx->shm_size, total); + ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>", + header->subcache_num, header->index_num); + if (non_empty_subcaches) { + apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); + ap_rprintf(r, "time left on oldest entries' objects: "); + if (now < average_expiry) + ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>", + (int)apr_time_sec(average_expiry - now), + (int)apr_time_sec(min_expiry - now), + (int)apr_time_sec(max_expiry - now)); + else + ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>"); + } + + ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>", + index_pct, cache_pct); + ap_rprintf(r, "total entries stored since starting: <b>%lu</b><br>", + header->stat_stores); + ap_rprintf(r, "total entries replaced since starting: <b>%lu</b><br>", + header->stat_replaced); + ap_rprintf(r, "total entries expired since starting: <b>%lu</b><br>", + header->stat_expiries); + ap_rprintf(r, "total (pre-expiry) entries scrolled out of the cache: " + "<b>%lu</b><br>", header->stat_scrolled); + ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, " + "<b>%lu</b> miss<br>", header->stat_retrieves_hit, + header->stat_retrieves_miss); + ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, " + "<b>%lu</b> miss<br>", header->stat_removes_hit, + header->stat_removes_miss); + } + else { + ap_rputs("CacheType: SHMCB\n", r); + ap_rprintf(r, "CacheSharedMemory: %" APR_SIZE_T_FMT "\n", + ctx->shm_size); + ap_rprintf(r, "CacheCurrentEntries: %d\n", total); + ap_rprintf(r, "CacheSubcaches: %d\n", header->subcache_num); + ap_rprintf(r, "CacheIndexesPerSubcaches: %d\n", header->index_num); + if (non_empty_subcaches) { + apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); + if (now < average_expiry) { + ap_rprintf(r, "CacheTimeLeftOldestAvg: %d\n", (int)apr_time_sec(average_expiry - now)); + ap_rprintf(r, "CacheTimeLeftOldestMin: %d\n", (int)apr_time_sec(min_expiry - now)); + ap_rprintf(r, "CacheTimeLeftOldestMax: %d\n", (int)apr_time_sec(max_expiry - now)); + } + } + + ap_rprintf(r, "CacheIndexUsage: %d%%\n", index_pct); + ap_rprintf(r, "CacheUsage: %d%%\n", cache_pct); + ap_rprintf(r, "CacheStoreCount: %lu\n", header->stat_stores); + ap_rprintf(r, "CacheReplaceCount: %lu\n", header->stat_replaced); + ap_rprintf(r, "CacheExpireCount: %lu\n", header->stat_expiries); + ap_rprintf(r, "CacheDiscardCount: %lu\n", header->stat_scrolled); + ap_rprintf(r, "CacheRetrieveHitCount: %lu\n", header->stat_retrieves_hit); + ap_rprintf(r, "CacheRetrieveMissCount: %lu\n", header->stat_retrieves_miss); + ap_rprintf(r, "CacheRemoveHitCount: %lu\n", header->stat_removes_hit); + ap_rprintf(r, "CacheRemoveMissCount: %lu\n", header->stat_removes_miss); + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00841) "leaving shmcb_status"); +} + +static apr_status_t socache_shmcb_iterate(ap_socache_instance_t *instance, + server_rec *s, void *userctx, + ap_socache_iterator_t *iterator, + apr_pool_t *pool) +{ + SHMCBHeader *header = instance->header; + unsigned int loop; + apr_time_t now = apr_time_now(); + apr_status_t rv = APR_SUCCESS; + apr_size_t buflen = 0; + unsigned char *buf = NULL; + + /* Perform the iteration inside the mutex to avoid corruption or invalid + * pointer arithmetic. The rest of our logic uses read-only header data so + * doesn't need the lock. */ + /* Iterate over the subcaches */ + for (loop = 0; loop < header->subcache_num && rv == APR_SUCCESS; loop++) { + SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); + rv = shmcb_subcache_iterate(instance, s, userctx, header, subcache, + iterator, &buf, &buflen, pool, now); + } + return rv; +} + +/* + * Subcache-level cache operations + */ + +static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, apr_time_t now) +{ + unsigned int loop = 0, freed = 0, expired = 0; + unsigned int new_idx_pos = subcache->idx_pos; + SHMCBIndex *idx = NULL; + + while (loop < subcache->idx_used) { + idx = SHMCB_INDEX(subcache, new_idx_pos); + if (idx->removed) + freed++; + else if (idx->expires <= now) + expired++; + else + /* not removed and not expired yet, we're done iterating */ + break; + loop++; + new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num); + } + if (!loop) + /* Nothing to do */ + return; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00842) + "expiring %u and reclaiming %u removed socache entries", + expired, freed); + if (loop == subcache->idx_used) { + /* We're expiring everything, piece of cake */ + subcache->idx_used = 0; + subcache->data_used = 0; + } else { + /* There remain other indexes, so we can use idx to adjust 'data' */ + unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos, + idx->data_pos, + header->subcache_data_size); + /* Adjust the indexes */ + subcache->idx_used -= loop; + subcache->idx_pos = new_idx_pos; + /* Adjust the data area */ + subcache->data_used -= diff; + subcache->data_pos = idx->data_pos; + } + header->stat_expiries += expired; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00843) + "we now have %u socache entries", subcache->idx_used); +} + +static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + unsigned char *data, unsigned int data_len, + const unsigned char *id, unsigned int id_len, + apr_time_t expiry) +{ + unsigned int data_offset, new_idx, id_offset; + SHMCBIndex *idx; + unsigned int total_len = id_len + data_len; + + /* Sanity check the input */ + if (total_len > header->subcache_data_size) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00844) + "inserting socache entry larger (%d) than subcache data area (%d)", + total_len, header->subcache_data_size); + return -1; + } + + /* First reclaim space from removed and expired records. */ + shmcb_subcache_expire(s, header, subcache, apr_time_now()); + + /* Loop until there is enough space to insert + * XXX: This should first compress out-of-order expiries and + * removed records, and then force-remove oldest-first + */ + if (header->subcache_data_size - subcache->data_used < total_len + || subcache->idx_used == header->index_num) { + unsigned int loop = 0; + + idx = SHMCB_INDEX(subcache, subcache->idx_pos); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845) + "about to force-expire, subcache: idx_used=%d, " + "data_used=%d", subcache->idx_used, subcache->data_used); + do { + SHMCBIndex *idx2; + + /* Adjust the indexes by one */ + subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1, + header->index_num); + subcache->idx_used--; + if (!subcache->idx_used) { + /* There's nothing left */ + subcache->data_used = 0; + break; + } + /* Adjust the data */ + idx2 = SHMCB_INDEX(subcache, subcache->idx_pos); + subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos, + header->subcache_data_size); + subcache->data_pos = idx2->data_pos; + /* Stats */ + header->stat_scrolled++; + /* Loop admin */ + idx = idx2; + loop++; + } while (header->subcache_data_size - subcache->data_used < total_len); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846) + "finished force-expire, subcache: idx_used=%d, " + "data_used=%d", subcache->idx_used, subcache->data_used); + } + + /* HERE WE ASSUME THAT THE NEW ENTRY SHOULD GO ON THE END! I'M NOT + * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE. + * + * We aught to fix that. httpd (never mind third party modules) + * does not promise to perform any processing in date order + * (c.f. FAQ "My log entries are not in date order!") + */ + /* Insert the id */ + id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, + header->subcache_data_size); + shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, + SHMCB_DATA(header, subcache), id_offset, + id, id_len); + subcache->data_used += id_len; + /* Insert the data */ + data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, + header->subcache_data_size); + shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, + SHMCB_DATA(header, subcache), data_offset, + data, data_len); + subcache->data_used += data_len; + /* Insert the index */ + new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used, + header->index_num); + idx = SHMCB_INDEX(subcache, new_idx); + idx->expires = expiry; + idx->data_pos = id_offset; + idx->data_used = total_len; + idx->id_len = id_len; + idx->removed = 0; + subcache->idx_used++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00847) + "insert happened at idx=%d, data=(%u:%u)", new_idx, + id_offset, data_offset); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00848) + "finished insert, subcache: idx_pos/idx_used=%d/%d, " + "data_pos/data_used=%d/%d", + subcache->idx_pos, subcache->idx_used, + subcache->data_pos, subcache->data_used); + return 0; +} + +static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen) +{ + unsigned int pos; + unsigned int loop = 0; + apr_time_t now = apr_time_now(); + + pos = subcache->idx_pos; + + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the id matches, and the "removed" + * flag isn't set, and the record is not expired. + * Check the data length too to avoid a buffer overflow + * in case of corruption, which should be impossible, + * but it's cheap to be safe. */ + if (!idx->removed + && idx->id_len == idlen + && (idx->data_used - idx->id_len) <= *destlen + && shmcb_cyclic_memcmp(header->subcache_data_size, + SHMCB_DATA(header, subcache), + idx->data_pos, id, idx->id_len) == 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00849) + "match at idx=%d, data=%d", pos, idx->data_pos); + if (idx->expires > now) { + unsigned int data_offset; + + /* Find the offset of the data segment, after the id */ + data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, + idx->id_len, + header->subcache_data_size); + + *destlen = idx->data_used - idx->id_len; + + /* Copy out the data */ + shmcb_cyclic_cton_memcpy(header->subcache_data_size, + dest, SHMCB_DATA(header, subcache), + data_offset, *destlen); + + return 0; + } + else { + /* Already stale, quietly remove and treat as not-found */ + idx->removed = 1; + header->stat_expiries++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00850) + "shmcb_subcache_retrieve discarding expired entry"); + return -1; + } + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00851) + "shmcb_subcache_retrieve found no match"); + return -1; +} + +static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + const unsigned char *id, + unsigned int idlen) +{ + unsigned int pos; + unsigned int loop = 0; + + pos = subcache->idx_pos; + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the id matches, and the "removed" + * flag isn't set. */ + if (!idx->removed && idx->id_len == idlen + && shmcb_cyclic_memcmp(header->subcache_data_size, + SHMCB_DATA(header, subcache), + idx->data_pos, id, idx->id_len) == 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00852) + "possible match at idx=%d, data=%d", pos, idx->data_pos); + + /* Found the matching entry, remove it quietly. */ + idx->removed = 1; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00853) + "shmcb_subcache_remove removing matching entry"); + return 0; + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + return -1; /* failure */ +} + + +static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, + server_rec *s, + void *userctx, + SHMCBHeader *header, + SHMCBSubcache *subcache, + ap_socache_iterator_t *iterator, + unsigned char **buf, + apr_size_t *buf_len, + apr_pool_t *pool, + apr_time_t now) +{ + unsigned int pos; + unsigned int loop = 0; + apr_status_t rv; + + pos = subcache->idx_pos; + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the "removed" flag isn't set. */ + if (!idx->removed) { + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00854) + "iterating idx=%d, data=%d", pos, idx->data_pos); + if (idx->expires > now) { + unsigned char *id = *buf; + unsigned char *dest; + unsigned int data_offset, dest_len; + apr_size_t buf_req; + + /* Find the offset of the data segment, after the id */ + data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, + idx->id_len, + header->subcache_data_size); + + dest_len = idx->data_used - idx->id_len; + + buf_req = APR_ALIGN_DEFAULT(idx->id_len + 1) + + APR_ALIGN_DEFAULT(dest_len + 1); + + if (buf_req > *buf_len) { + /* Grow to ~150% of this buffer requirement on resize + * always using APR_ALIGN_DEFAULT sized pages + */ + *buf_len = buf_req + APR_ALIGN_DEFAULT(buf_req / 2); + *buf = apr_palloc(pool, *buf_len); + id = *buf; + } + + dest = *buf + APR_ALIGN_DEFAULT(idx->id_len + 1); + + /* Copy out the data, because it's potentially cyclic */ + shmcb_cyclic_cton_memcpy(header->subcache_data_size, id, + SHMCB_DATA(header, subcache), + idx->data_pos, idx->id_len); + id[idx->id_len] = '\0'; + + shmcb_cyclic_cton_memcpy(header->subcache_data_size, dest, + SHMCB_DATA(header, subcache), + data_offset, dest_len); + dest[dest_len] = '\0'; + + rv = iterator(instance, s, userctx, id, idx->id_len, + dest, dest_len, pool); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00855) + "shmcb entry iterated"); + if (rv != APR_SUCCESS) + return rv; + } + else { + /* Already stale, quietly remove and treat as not-found */ + idx->removed = 1; + header->stat_expiries++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00856) + "shmcb_subcache_iterate discarding expired entry"); + } + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + return APR_SUCCESS; +} + +static const ap_socache_provider_t socache_shmcb = { + "shmcb", + AP_SOCACHE_FLAG_NOTMPSAFE, + socache_shmcb_create, + socache_shmcb_init, + socache_shmcb_destroy, + socache_shmcb_store, + socache_shmcb_retrieve, + socache_shmcb_remove, + socache_shmcb_status, + socache_shmcb_iterate +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", + AP_SOCACHE_PROVIDER_VERSION, + &socache_shmcb); + + /* Also register shmcb under the default provider name. */ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, + AP_SOCACHE_DEFAULT_PROVIDER, + AP_SOCACHE_PROVIDER_VERSION, + &socache_shmcb); +} + +AP_DECLARE_MODULE(socache_shmcb) = { + STANDARD20_MODULE_STUFF, + NULL, NULL, NULL, NULL, NULL, + register_hooks +}; diff --git a/modules/cache/mod_socache_shmcb.dep b/modules/cache/mod_socache_shmcb.dep new file mode 100644 index 0000000..a5802b2 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.dep @@ -0,0 +1,56 @@ +# Microsoft Developer Studio Generated Dependency File, included by mod_socache_shmcb.mak + +..\..\build\win32\httpd.rc : \ + "..\..\include\ap_release.h"\ + + +.\mod_socache_shmcb.c : \ + "..\..\include\ap_config.h"\ + "..\..\include\ap_config_layout.h"\ + "..\..\include\ap_hooks.h"\ + "..\..\include\ap_mmn.h"\ + "..\..\include\ap_provider.h"\ + "..\..\include\ap_regex.h"\ + "..\..\include\ap_release.h"\ + "..\..\include\ap_socache.h"\ + "..\..\include\apache_noprobes.h"\ + "..\..\include\http_config.h"\ + "..\..\include\http_log.h"\ + "..\..\include\http_protocol.h"\ + "..\..\include\http_request.h"\ + "..\..\include\httpd.h"\ + "..\..\include\os.h"\ + "..\..\include\util_cfgtree.h"\ + "..\..\include\util_filter.h"\ + "..\..\srclib\apr-util\include\apr_buckets.h"\ + "..\..\srclib\apr-util\include\apr_hooks.h"\ + "..\..\srclib\apr-util\include\apr_optional.h"\ + "..\..\srclib\apr-util\include\apr_optional_hooks.h"\ + "..\..\srclib\apr-util\include\apr_uri.h"\ + "..\..\srclib\apr-util\include\apu.h"\ + "..\..\srclib\apr\include\apr.h"\ + "..\..\srclib\apr\include\apr_allocator.h"\ + "..\..\srclib\apr\include\apr_dso.h"\ + "..\..\srclib\apr\include\apr_errno.h"\ + "..\..\srclib\apr\include\apr_file_info.h"\ + "..\..\srclib\apr\include\apr_file_io.h"\ + "..\..\srclib\apr\include\apr_general.h"\ + "..\..\srclib\apr\include\apr_global_mutex.h"\ + "..\..\srclib\apr\include\apr_inherit.h"\ + "..\..\srclib\apr\include\apr_mmap.h"\ + "..\..\srclib\apr\include\apr_network_io.h"\ + "..\..\srclib\apr\include\apr_poll.h"\ + "..\..\srclib\apr\include\apr_pools.h"\ + "..\..\srclib\apr\include\apr_portable.h"\ + "..\..\srclib\apr\include\apr_proc_mutex.h"\ + "..\..\srclib\apr\include\apr_ring.h"\ + "..\..\srclib\apr\include\apr_shm.h"\ + "..\..\srclib\apr\include\apr_strings.h"\ + "..\..\srclib\apr\include\apr_tables.h"\ + "..\..\srclib\apr\include\apr_thread_mutex.h"\ + "..\..\srclib\apr\include\apr_thread_proc.h"\ + "..\..\srclib\apr\include\apr_time.h"\ + "..\..\srclib\apr\include\apr_user.h"\ + "..\..\srclib\apr\include\apr_want.h"\ + "..\generators\mod_status.h"\ + diff --git a/modules/cache/mod_socache_shmcb.dsp b/modules/cache/mod_socache_shmcb.dsp new file mode 100644 index 0000000..17c1008 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="mod_socache_shmcb" - 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_socache_shmcb - 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_socache_shmcb.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_socache_shmcb.mak" CFG="mod_socache_shmcb - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_shmcb - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_shmcb - 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_socache_shmcb - 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_socache_shmcb_EXPORTS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Release\mod_socache_shmcb_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_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_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_socache_shmcb.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so /opt:ref +# Begin Special Build Tool +TargetPath=.\Release\mod_socache_shmcb.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - 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" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fd"Debug\mod_socache_shmcb_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_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_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_socache_shmcb.so" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so +# Begin Special Build Tool +TargetPath=.\Debug\mod_socache_shmcb.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2 +# End Special Build Tool + +!ENDIF + +# Begin Target + +# Name "mod_socache_shmcb - Win32 Release" +# Name "mod_socache_shmcb - Win32 Debug" +# Begin Source File + +SOURCE=.\mod_socache_shmcb.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_socache_shmcb.mak b/modules/cache/mod_socache_shmcb.mak new file mode 100644 index 0000000..7081784 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.mak @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Generated NMAKE File, Based on mod_socache_shmcb.dsp +!IF "$(CFG)" == "" +CFG=mod_socache_shmcb - Win32 Debug +!MESSAGE No configuration specified. Defaulting to mod_socache_shmcb - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "mod_socache_shmcb - Win32 Release" && "$(CFG)" != "mod_socache_shmcb - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!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_socache_shmcb.mak" CFG="mod_socache_shmcb - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mod_socache_shmcb - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mod_socache_shmcb - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" + +OUTDIR=.\Release +INTDIR=.\Release +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Release" "libaprutil - Win32 Release" "libapr - Win32 Release" "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 ReleaseCLEAN" "libaprutil - Win32 ReleaseCLEAN" "libhttpd - Win32 ReleaseCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_shmcb.obj" + -@erase "$(INTDIR)\mod_socache_shmcb.res" + -@erase "$(INTDIR)\mod_socache_shmcb_src.idb" + -@erase "$(INTDIR)\mod_socache_shmcb_src.pdb" + -@erase "$(OUTDIR)\mod_socache_shmcb.exp" + -@erase "$(OUTDIR)\mod_socache_shmcb.lib" + -@erase "$(OUTDIR)\mod_socache_shmcb.pdb" + -@erase "$(OUTDIR)\mod_socache_shmcb.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MD /W3 /Zi /O2 /Oy- /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_shmcb_src" /FD /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_shmcb.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_shmcb.pdb" /debug /out:"$(OUTDIR)\mod_socache_shmcb.so" /implib:"$(OUTDIR)\mod_socache_shmcb.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so /opt:ref +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_shmcb.obj" \ + "$(INTDIR)\mod_socache_shmcb.res" \ + "..\..\srclib\apr\Release\libapr-1.lib" \ + "..\..\srclib\apr-util\Release\libaprutil-1.lib" \ + "..\..\Release\libhttpd.lib" + +"$(OUTDIR)\mod_socache_shmcb.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Release\mod_socache_shmcb.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_shmcb.so" + if exist .\Release\mod_socache_shmcb.so.manifest mt.exe -manifest .\Release\mod_socache_shmcb.so.manifest -outputresource:.\Release\mod_socache_shmcb.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + +OUTDIR=.\Debug +INTDIR=.\Debug +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +!IF "$(RECURSE)" == "0" + +ALL : "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)" + +!ELSE + +ALL : "libhttpd - Win32 Debug" "libaprutil - Win32 Debug" "libapr - Win32 Debug" "$(OUTDIR)\mod_socache_shmcb.so" "$(DS_POSTBUILD_DEP)" + +!ENDIF + +!IF "$(RECURSE)" == "1" +CLEAN :"libapr - Win32 DebugCLEAN" "libaprutil - Win32 DebugCLEAN" "libhttpd - Win32 DebugCLEAN" +!ELSE +CLEAN : +!ENDIF + -@erase "$(INTDIR)\mod_socache_shmcb.obj" + -@erase "$(INTDIR)\mod_socache_shmcb.res" + -@erase "$(INTDIR)\mod_socache_shmcb_src.idb" + -@erase "$(INTDIR)\mod_socache_shmcb_src.pdb" + -@erase "$(OUTDIR)\mod_socache_shmcb.exp" + -@erase "$(OUTDIR)\mod_socache_shmcb.lib" + -@erase "$(OUTDIR)\mod_socache_shmcb.pdb" + -@erase "$(OUTDIR)\mod_socache_shmcb.so" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP=cl.exe +CPP_PROJ=/nologo /MDd /W3 /Zi /Od /I "../../srclib/apr-util/include" /I "../../srclib/apr/include" /I "../../include" /I "../generators" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\mod_socache_shmcb_src" /FD /EHsc /c + +.c{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.c{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cxx{$(INTDIR)}.sbr:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +MTL=midl.exe +MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32 +RSC=rc.exe +RSC_PROJ=/l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" +BSC32=bscmake.exe +BSC32_FLAGS=/nologo /o"$(OUTDIR)\mod_socache_shmcb.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)\mod_socache_shmcb.pdb" /debug /out:"$(OUTDIR)\mod_socache_shmcb.so" /implib:"$(OUTDIR)\mod_socache_shmcb.lib" /base:@..\..\os\win32\BaseAddr.ref,mod_socache_shmcb.so +LINK32_OBJS= \ + "$(INTDIR)\mod_socache_shmcb.obj" \ + "$(INTDIR)\mod_socache_shmcb.res" \ + "..\..\srclib\apr\Debug\libapr-1.lib" \ + "..\..\srclib\apr-util\Debug\libaprutil-1.lib" \ + "..\..\Debug\libhttpd.lib" + +"$(OUTDIR)\mod_socache_shmcb.so" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +TargetPath=.\Debug\mod_socache_shmcb.so +SOURCE="$(InputPath)" +PostBuild_Desc=Embed .manifest +DS_POSTBUILD_DEP=$(INTDIR)\postbld.dep + +# Begin Custom Macros +OutDir=.\Debug +# End Custom Macros + +"$(DS_POSTBUILD_DEP)" : "$(OUTDIR)\mod_socache_shmcb.so" + if exist .\Debug\mod_socache_shmcb.so.manifest mt.exe -manifest .\Debug\mod_socache_shmcb.so.manifest -outputresource:.\Debug\mod_socache_shmcb.so;2 + echo Helper for Post-build step > "$(DS_POSTBUILD_DEP)" + +!ENDIF + + +!IF "$(NO_EXTERNAL_DEPS)" != "1" +!IF EXISTS("mod_socache_shmcb.dep") +!INCLUDE "mod_socache_shmcb.dep" +!ELSE +!MESSAGE Warning: cannot find "mod_socache_shmcb.dep" +!ENDIF +!ENDIF + + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" || "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" + +"libapr - Win32 Release" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" + cd "..\..\modules\cache" + +"libapr - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + +"libapr - Win32 Debug" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" + cd "..\..\modules\cache" + +"libapr - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr" + $(MAKE) /$(MAKEFLAGS) /F ".\libapr.mak" CFG="libapr - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" + +"libaprutil - Win32 Release" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" + cd "..\..\modules\cache" + +"libaprutil - Win32 ReleaseCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Release" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + +"libaprutil - Win32 Debug" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" + cd "..\..\modules\cache" + +"libaprutil - Win32 DebugCLEAN" : + cd ".\..\..\srclib\apr-util" + $(MAKE) /$(MAKEFLAGS) /F ".\libaprutil.mak" CFG="libaprutil - Win32 Debug" RECURSE=1 CLEAN + cd "..\..\modules\cache" + +!ENDIF + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" + +"libhttpd - Win32 Release" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" + cd ".\modules\cache" + +"libhttpd - Win32 ReleaseCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Release" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + +"libhttpd - Win32 Debug" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" + cd ".\modules\cache" + +"libhttpd - Win32 DebugCLEAN" : + cd ".\..\.." + $(MAKE) /$(MAKEFLAGS) /F ".\libhttpd.mak" CFG="libhttpd - Win32 Debug" RECURSE=1 CLEAN + cd ".\modules\cache" + +!ENDIF + +SOURCE=..\..\build\win32\httpd.rc + +!IF "$(CFG)" == "mod_socache_shmcb - Win32 Release" + + +"$(INTDIR)\mod_socache_shmcb.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "NDEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" $(SOURCE) + + +!ELSEIF "$(CFG)" == "mod_socache_shmcb - Win32 Debug" + + +"$(INTDIR)\mod_socache_shmcb.res" : $(SOURCE) "$(INTDIR)" + $(RSC) /l 0x409 /fo"$(INTDIR)\mod_socache_shmcb.res" /i "../../include" /i "../../srclib/apr/include" /i "../../build\win32" /d "_DEBUG" /d BIN_NAME="mod_socache_shmcb.so" /d LONG_NAME="socache_shmcb_module for Apache" $(SOURCE) + + +!ENDIF + +SOURCE=.\mod_socache_shmcb.c + +"$(INTDIR)\mod_socache_shmcb.obj" : $(SOURCE) "$(INTDIR)" + + + +!ENDIF + |