2009-07-04 18:08:14 +00:00
/*
* mod_expire - add " Expires " and " Cache-Control " headers to response
*
* Description :
* mod_expire lets you control client - side caching of responses based on a simple rule / formula .
* If a response is cached using an " Expires " and " Cache-Control " header , then the client will not issue a new
* request for it until the date specified by the header is reached .
*
* The rule / formula used here , complies with the one mod_expire for Apache uses :
* < base > [ plus ] ( < num > < type > ) +
* Where < base > is one of " access " , " now " or " modification " - " now " being equivalent to " access " .
* < plus > is optional and does nothing .
* < num > is any positive integer .
* < type > is one of " seconds, " minutes " , " hours " , " days " , " weeks " , " months " or " years " .
* The trailing s in < type > is optional .
*
* If you use " modification " as < base > and the file does not exist or cannot be accessed ,
* mod_expire will do nothing and request processing will go on .
*
2009-07-04 19:55:32 +00:00
* The expire action will overwrite any existing " Expires " header .
* It will append the max - age value to any existing " Cache-Control " header .
2009-07-04 18:08:14 +00:00
*
* Setups :
* none
* Options :
* none
* Actions :
* expire " rule " ;
* - adds an Expires header to the response based on the specified rule .
*
* Example config :
* if request . path = ~ " ^/(css|js|images)/ " {
* expire " access plus 1 day " ;
* }
*
*
* Tip :
* Adding expire headers to static content like css , javascript , images or similar ,
* can greatly reduce the amount of requests you get and therefor save resources .
* Use " modification " as < base > if your content changes in specific intervals like every 15 minutes .
*
* Todo :
* none
*
* Author :
* Copyright ( c ) 2009 Thomas Porzelt
* License :
* MIT , see COPYING file in the lighttpd 2 tree
*/
# include <lighttpd/base.h>
2009-07-08 19:06:07 +00:00
LI_API gboolean mod_expire_init ( liModules * mods , liModule * mod ) ;
LI_API gboolean mod_expire_free ( liModules * mods , liModule * mod ) ;
2009-07-04 18:08:14 +00:00
struct expire_rule {
enum {
EXPIRE_ACCESS ,
EXPIRE_MODIFICATION
} base ;
guint num ;
} ;
typedef struct expire_rule expire_rule ;
2009-07-08 19:06:07 +00:00
static liHandlerResult expire ( liVRequest * vr , gpointer param , gpointer * context ) {
2009-07-04 18:08:14 +00:00
struct tm tm ;
2009-07-04 19:55:32 +00:00
time_t expire_date ;
2009-07-04 18:08:14 +00:00
guint len ;
2009-07-04 19:04:27 +00:00
gint max_age ;
2009-07-04 18:08:14 +00:00
GString * date_str = vr - > wrk - > tmp_str ;
expire_rule * rule = param ;
2009-07-04 19:55:32 +00:00
guint num = rule - > num ;
2009-07-04 19:04:27 +00:00
time_t now = ( time_t ) CUR_TS ( vr - > wrk ) ;
2009-07-04 18:08:14 +00:00
UNUSED ( context ) ;
if ( rule - > base = = EXPIRE_ACCESS ) {
2009-07-04 19:55:32 +00:00
expire_date = now + num ;
2009-07-04 19:04:27 +00:00
max_age = num ;
2009-07-04 18:08:14 +00:00
} else {
/* modification */
struct stat st ;
gint err ;
2009-07-04 19:55:32 +00:00
if ( ! vr - > physical . path - > len )
2009-07-08 19:06:07 +00:00
return LI_HANDLER_GO_ON ;
2009-07-04 19:55:32 +00:00
2009-07-09 20:17:24 +00:00
switch ( li_stat_cache_get ( vr , vr - > physical . path , & st , & err , NULL ) ) {
2009-07-08 19:06:07 +00:00
case LI_HANDLER_GO_ON : break ;
case LI_HANDLER_WAIT_FOR_EVENT : return LI_HANDLER_WAIT_FOR_EVENT ;
default : return LI_HANDLER_GO_ON ;
2009-07-04 18:08:14 +00:00
}
2009-07-04 19:55:32 +00:00
expire_date = st . st_mtime + num ;
if ( expire_date < now )
expire_date = now ;
max_age = expire_date - now ;
2009-07-04 18:08:14 +00:00
}
/* format date */
g_string_set_size ( date_str , 255 ) ;
2009-07-04 20:06:48 +00:00
if ( ! gmtime_r ( & expire_date , & tm ) ) {
VR_ERROR ( vr , " gmtime_r(% " G_GUINT64_FORMAT " ) failed: %s " , ( guint64 ) expire_date , g_strerror ( errno ) ) ;
2009-07-08 19:06:07 +00:00
return LI_HANDLER_GO_ON ;
2009-07-04 20:06:48 +00:00
}
2009-07-04 18:08:14 +00:00
len = strftime ( date_str - > str , date_str - > allocated_len , " %a, %d %b %Y %H:%M:%S GMT " , & tm ) ;
if ( len = = 0 )
2009-07-08 19:06:07 +00:00
return LI_HANDLER_GO_ON ;
2009-07-04 18:08:14 +00:00
g_string_set_size ( date_str , len ) ;
/* finally set the headers */
2009-07-09 20:17:24 +00:00
li_http_header_overwrite ( vr - > response . headers , CONST_STR_LEN ( " Expires " ) , GSTR_LEN ( date_str ) ) ;
2009-07-04 18:08:14 +00:00
g_string_truncate ( date_str , 0 ) ;
g_string_append_len ( date_str , CONST_STR_LEN ( " max-age= " ) ) ;
2009-07-09 20:17:24 +00:00
li_string_append_int ( date_str , max_age ) ;
li_http_header_append ( vr - > response . headers , CONST_STR_LEN ( " Cache-Control " ) , GSTR_LEN ( date_str ) ) ;
2009-07-04 18:08:14 +00:00
2009-07-08 19:06:07 +00:00
return LI_HANDLER_GO_ON ;
2009-07-04 18:08:14 +00:00
}
2009-07-08 19:06:07 +00:00
static void expire_free ( liServer * srv , gpointer param ) {
2009-07-04 18:08:14 +00:00
UNUSED ( srv ) ;
g_slice_free ( expire_rule , param ) ;
}
2009-07-08 19:06:07 +00:00
static liAction * expire_create ( liServer * srv , liPlugin * p , liValue * val ) {
2009-07-04 18:08:14 +00:00
expire_rule * rule ;
gchar * str ;
UNUSED ( srv ) ;
UNUSED ( p ) ;
2009-07-08 19:06:07 +00:00
if ( ! val | | val - > type ! = LI_VALUE_STRING ) {
2009-07-04 18:08:14 +00:00
ERROR ( srv , " %s " , " expire expects a string as parameter " ) ;
return NULL ;
}
rule = g_slice_new ( expire_rule ) ;
str = val - > data . string - > str ;
/* check if we have "access", "now" or "modification as <base> */
if ( g_str_has_prefix ( str , " access " ) ) {
rule - > base = EXPIRE_ACCESS ;
str + = sizeof ( " access " ) - 1 ;
} else if ( g_str_has_prefix ( str , " now " ) ) {
rule - > base = EXPIRE_ACCESS ;
str + = sizeof ( " now " ) - 1 ;
} else if ( g_str_has_prefix ( str , " modification " ) ) {
rule - > base = EXPIRE_MODIFICATION ;
str + = sizeof ( " modification " ) - 1 ;
} else {
g_slice_free ( expire_rule , rule ) ;
ERROR ( srv , " expire: error parsing rule \" %s \" " , val - > data . string - > str ) ;
return NULL ;
}
/* skip the optional "plus", it does nothing */
if ( g_str_has_prefix ( str , " plus " ) )
str + = sizeof ( " plus " ) - 1 ;
/* parse <num> */
rule - > num = 0 ;
for ( ; * str ; str + + ) {
if ( * str < ' 0 ' | | * str > ' 9 ' )
break ;
rule - > num + = ( * str ) - ' 0 ' ;
}
if ( ! rule - > num ) {
g_slice_free ( expire_rule , rule ) ;
ERROR ( srv , " expire: error parsing rule \" %s \" , <num> must be a positive integer " , val - > data . string - > str ) ;
return NULL ;
}
/* parse <type> */
if ( g_str_equal ( str , " second " ) | | g_str_equal ( str , " seconds " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 1 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " minute " ) | | g_str_equal ( str , " minutes " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 60 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " hour " ) | | g_str_equal ( str , " hours " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 3600 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " day " ) | | g_str_equal ( str , " days " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 3600 * 24 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " week " ) | | g_str_equal ( str , " weeks " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 3600 * 24 * 7 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " month " ) | | g_str_equal ( str , " months " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 3600 * 24 * 30 ;
2009-07-04 18:08:14 +00:00
else if ( g_str_equal ( str , " year " ) | | g_str_equal ( str , " years " ) )
2009-07-04 20:25:05 +00:00
rule - > num * = 3600 * 24 * 365 ;
2009-07-04 18:08:14 +00:00
else {
g_slice_free ( expire_rule , rule ) ;
ERROR ( srv , " expire: error parsing rule \" %s \" , <type> must be one of 'seconds', 'minutes', 'hours', 'days', 'weeks', 'months' or 'years' " , val - > data . string - > str ) ;
return NULL ;
}
2009-07-09 20:17:24 +00:00
return li_action_new_function ( expire , NULL , expire_free , rule ) ;
2009-07-04 18:08:14 +00:00
}
2009-07-08 19:06:07 +00:00
static const liPluginOption options [ ] = {
2009-07-04 18:08:14 +00:00
{ NULL , 0 , NULL , NULL , NULL }
} ;
2009-07-08 19:06:07 +00:00
static const liPluginAction actions [ ] = {
2009-07-04 18:08:14 +00:00
{ " expire " , expire_create } ,
{ NULL , NULL }
} ;
2009-07-08 19:06:07 +00:00
static const liliPluginSetupCB setups [ ] = {
2009-07-04 18:08:14 +00:00
{ NULL , NULL }
} ;
2009-07-08 19:06:07 +00:00
static void plugin_expire_init ( liServer * srv , liPlugin * p ) {
2009-07-04 18:08:14 +00:00
UNUSED ( srv ) ;
p - > options = options ;
p - > actions = actions ;
p - > setups = setups ;
}
2009-07-08 19:06:07 +00:00
gboolean mod_expire_init ( liModules * mods , liModule * mod ) {
2009-07-04 18:08:14 +00:00
UNUSED ( mod ) ;
MODULE_VERSION_CHECK ( mods ) ;
2009-07-09 20:17:24 +00:00
mod - > config = li_plugin_register ( mods - > main , " mod_expire " , plugin_expire_init ) ;
2009-07-04 18:08:14 +00:00
return mod - > config ! = NULL ;
}
2009-07-08 19:06:07 +00:00
gboolean mod_expire_free ( liModules * mods , liModule * mod ) {
2009-07-04 18:08:14 +00:00
if ( mod - > config )
2009-07-09 20:17:24 +00:00
li_plugin_free ( mods - > main , mod - > config ) ;
2009-07-04 18:08:14 +00:00
return TRUE ;
}