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 ;
2013-05-18 13:27:59 +00:00
time_t now = ( time_t ) li_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 ) ;
}
2010-05-07 18:54:50 +00:00
static liAction * expire_create ( liServer * srv , liWorker * wrk , liPlugin * p , liValue * val , gpointer userdata ) {
2009-07-04 18:08:14 +00:00
expire_rule * rule ;
gchar * str ;
2010-05-07 18:54:50 +00:00
UNUSED ( wrk ) ; UNUSED ( p ) ; UNUSED ( userdata ) ;
2009-07-04 18:08:14 +00:00
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 ;
rule - > num = 0 ;
2009-09-30 09:35:04 +00:00
/* parse (<num> <type>)+ */
while ( * str ) {
guint num ;
/* parse <num> */
num = 0 ;
2009-07-04 18:08:14 +00:00
2009-09-30 09:35:04 +00:00
for ( ; * str ; str + + ) {
if ( * str < ' 0 ' | | * str > ' 9 ' )
break ;
2009-07-04 18:08:14 +00:00
2009-09-30 09:35:04 +00:00
num * = 10 ;
num + = ( * str ) - ' 0 ' ;
}
2009-07-04 18:08:14 +00:00
2009-09-30 09:35:04 +00:00
if ( ! 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_has_prefix ( str , " second " ) ) {
num * = 1 ;
str + = sizeof ( " second " ) - 1 ;
} else if ( g_str_has_prefix ( str , " minute " ) ) {
num * = 60 ;
str + = sizeof ( " minute " ) - 1 ;
} else if ( g_str_has_prefix ( str , " hour " ) ) {
num * = 3600 ;
str + = sizeof ( " hour " ) - 1 ;
} else if ( g_str_has_prefix ( str , " day " ) ) {
num * = 3600 * 24 ;
str + = sizeof ( " day " ) - 1 ;
} else if ( g_str_has_prefix ( str , " week " ) ) {
num * = 3600 * 24 * 7 ;
str + = sizeof ( " week " ) - 1 ;
} else if ( g_str_has_prefix ( str , " month " ) ) {
num * = 3600 * 24 * 30 ;
str + = sizeof ( " month " ) - 1 ;
} else if ( g_str_has_prefix ( str , " year " ) ) {
num * = 3600 * 24 * 365 ;
str + = sizeof ( " year " ) - 1 ;
} 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 ;
}
rule - > num + = num ;
if ( * str = = ' s ' )
str + + ;
if ( * str = = ' ' )
str + + ;
else if ( * str ) {
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-04 18:08:14 +00:00
}
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 [ ] = {
2010-01-24 20:30:41 +00:00
{ NULL , 0 , 0 , NULL }
2009-07-04 18:08:14 +00:00
} ;
2009-07-08 19:06:07 +00:00
static const liPluginAction actions [ ] = {
2009-12-21 11:29:14 +00:00
{ " expire " , expire_create , NULL } ,
2009-07-04 18:08:14 +00:00
2009-12-21 11:29:14 +00:00
{ NULL , NULL , NULL }
2009-07-04 18:08:14 +00:00
} ;
2009-08-30 17:25:01 +00:00
static const liPluginSetup setups [ ] = {
2009-12-21 11:29:14 +00:00
{ NULL , NULL , NULL }
2009-07-04 18:08:14 +00:00
} ;
2009-12-21 11:29:14 +00:00
static void plugin_expire_init ( liServer * srv , liPlugin * p , gpointer userdata ) {
UNUSED ( srv ) ; UNUSED ( userdata ) ;
2009-07-04 18:08:14 +00:00
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-12-21 11:29:14 +00:00
mod - > config = li_plugin_register ( mods - > main , " mod_expire " , plugin_expire_init , NULL ) ;
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 ;
}