[modules] Add mod_expire

personal/stbuehler/wip
Thomas Porzelt 14 years ago
parent 7aa2e0ef14
commit 2cffeb7401

@ -351,6 +351,7 @@ ADD_AND_INSTALL_LIBRARY(mod_balancer "modules/mod_balancer.c")
ADD_AND_INSTALL_LIBRARY(mod_cache_disk_etag "modules/mod_cache_disk_etag.c")
ADD_AND_INSTALL_LIBRARY(mod_debug "modules/mod_debug.c")
ADD_AND_INSTALL_LIBRARY(mod_dirlist "modules/mod_dirlist.c")
ADD_AND_INSTALL_LIBRARY(mod_expire "modules/mod_expire.c")
ADD_AND_INSTALL_LIBRARY(mod_fastcgi "modules/mod_fastcgi.c")
ADD_AND_INSTALL_LIBRARY(mod_fortune "modules/mod_fortune.c")
ADD_AND_INSTALL_LIBRARY(mod_rewrite "modules/mod_rewrite.c")

@ -0,0 +1,261 @@
/*
* 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.
*
* The expire action will overwrite any existing "Expires" or "Cache-Control" headers.
*
* 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 <stdio.h>
#include <lighttpd/base.h>
LI_API gboolean mod_expire_init(modules *mods, module *mod);
LI_API gboolean mod_expire_free(modules *mods, module *mod);
struct expire_rule {
enum {
EXPIRE_ACCESS,
EXPIRE_MODIFICATION
} base;
guint num;
enum {
EXPIRE_SECONDS,
EXPIRE_MINUTES,
EXPIRE_HOURS,
EXPIRE_DAYS,
EXPIRE_WEEKS,
EXPIRE_MONTHS,
EXPIRE_YEARS
} type;
};
typedef struct expire_rule expire_rule;
static handler_t expire(vrequest *vr, gpointer param, gpointer *context) {
struct tm tm;
time_t date;
guint len;
GString *date_str = vr->wrk->tmp_str;
expire_rule *rule = param;
guint num = ((expire_rule*)param)->num;
UNUSED(context);
if (!vr->physical.path->len)
return HANDLER_GO_ON;
switch (rule->type) {
case EXPIRE_SECONDS: num *= 1; break;
case EXPIRE_MINUTES: num *= 60; break;
case EXPIRE_HOURS: num *= 3600; break;
case EXPIRE_DAYS: num *= 3600*24; break;
case EXPIRE_WEEKS: num *= 3600*24*7; break;
case EXPIRE_MONTHS: num *= 3600*24*30; break;
case EXPIRE_YEARS: num *= 3600*24*365; break;
}
if (rule->base == EXPIRE_ACCESS) {
date = (time_t) (CUR_TS(vr->wrk) + num);
} else {
/* modification */
struct stat st;
gint err;
switch (stat_cache_get(vr, vr->physical.path, &st, &err, NULL)) {
case HANDLER_GO_ON: break;
case HANDLER_WAIT_FOR_EVENT: return HANDLER_WAIT_FOR_EVENT;
default: return HANDLER_GO_ON;
}
date = st.st_mtime + num;
}
/* format date */
g_string_set_size(date_str, 255);
if (!gmtime_r(&date, &tm))
return HANDLER_GO_ON;
len = strftime(date_str->str, date_str->allocated_len, "%a, %d %b %Y %H:%M:%S GMT", &tm);
if (len == 0)
return HANDLER_GO_ON;
g_string_set_size(date_str, len);
/* finally set the headers */
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Expires"), GSTR_LEN(date_str));
g_string_truncate(date_str, 0);
g_string_append_len(date_str, CONST_STR_LEN("max-age="));
l_g_string_append_int(date_str, num);
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Cache-Control"), GSTR_LEN(date_str));
return HANDLER_GO_ON;
}
static void expire_free(server *srv, gpointer param) {
UNUSED(srv);
g_slice_free(expire_rule, param);
}
static action* expire_create(server *srv, plugin* p, value *val) {
expire_rule *rule;
gchar *str;
UNUSED(srv);
UNUSED(p);
if (!val || val->type != VALUE_STRING) {
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"))
rule->type = EXPIRE_SECONDS;
else if (g_str_equal(str, " minute") || g_str_equal(str, " minutes"))
rule->type = EXPIRE_MINUTES;
else if (g_str_equal(str, " hour") || g_str_equal(str, " hours"))
rule->type = EXPIRE_HOURS;
else if (g_str_equal(str, " day") || g_str_equal(str, " days"))
rule->type = EXPIRE_DAYS;
else if (g_str_equal(str, " week") || g_str_equal(str, " weeks"))
rule->type = EXPIRE_WEEKS;
else if (g_str_equal(str, " month") || g_str_equal(str, " months"))
rule->type = EXPIRE_MONTHS;
else if (g_str_equal(str, " year") || g_str_equal(str, " years"))
rule->type = EXPIRE_YEARS;
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;
}
return action_new_function(expire, NULL, expire_free, rule);
}
static const plugin_option options[] = {
{ NULL, 0, NULL, NULL, NULL }
};
static const plugin_action actions[] = {
{ "expire", expire_create },
{ NULL, NULL }
};
static const plugin_setup setups[] = {
{ NULL, NULL }
};
static void plugin_expire_init(server *srv, plugin *p) {
UNUSED(srv);
p->options = options;
p->actions = actions;
p->setups = setups;
}
gboolean mod_expire_init(modules *mods, module *mod) {
UNUSED(mod);
MODULE_VERSION_CHECK(mods);
mod->config = plugin_register(mods->main, "mod_expire", plugin_expire_init);
return mod->config != NULL;
}
gboolean mod_expire_free(modules *mods, module *mod) {
if (mod->config)
plugin_free(mods->main, mod->config);
return TRUE;
}
Loading…
Cancel
Save