diff --git a/configure.in b/configure.in index 549023ab..5b3fbe3f 100644 --- a/configure.in +++ b/configure.in @@ -271,7 +271,20 @@ AC_ARG_WITH(memcache, AC_HELP_STRING([--with-memcache],[memcached storage for mo ]) ],[AC_MSG_RESULT(no)]) AC_SUBST(MEMCACHE_LIB) - + +AC_MSG_CHECKING(for lua) +AC_ARG_WITH(lua, AC_HELP_STRING([--with-lua],[lua engine for mod_cml]), +[AC_MSG_RESULT(yes) + AC_CHECK_LIB(lua, lua_open, [ + AC_CHECK_HEADERS([lua.h],[ + LUA_LIB="-llua -llualib -lm" + AC_DEFINE([HAVE_LUA], [1], [liblua]) + AC_DEFINE([HAVE_LUA_H], [1]) + ]) + ]) +],[AC_MSG_RESULT(no)]) +AC_SUBST(LUA_LIB) + AC_SEARCH_LIBS(socket,socket) AC_SEARCH_LIBS(gethostbyname,nsl socket) diff --git a/doc/cml.txt b/doc/cml.txt index d0f9b694..fc086229 100644 --- a/doc/cml.txt +++ b/doc/cml.txt @@ -11,10 +11,10 @@ Module: mod_cml :Revision: $Revision: 1.2 $ :abstract: - CML is a Meta language to describe the dependencies of a page at one side and building a page from its fragments on the other side + CML is a Meta language to describe the dependencies of a page at one side and building a page from its fragments on the other side using LUA. .. meta:: - :keywords: lighttpd, cml + :keywords: lighttpd, cml, lua .. contents:: Table of Contents @@ -97,14 +97,22 @@ to start PHP for a cache-hit. To transform this example into a CML you need 'index.cml' in the list of indexfiles and the following index.cml file: :: - output.content-type text/html + output_contenttype = "text/html" - output.include _cache.html + b = request["DOCUMENT_ROOT"] + cwd = request["CWD"] - trigger.handler index.php - trigger.if file.mtime("../lib/php/menu.csv") > file.mtime("_cache.html") - trigger.if file.mtime("templates/jk.tmpl") > file.mtime("_cache.html") - trigger.if file.mtime("content.html") > file.mtime("_cache.html") + output_include = { b + "_cache.html" } + + trigger_handler = "index.php" + + if file_mtime(b + "../lib/php/menu.csv") > file_mtime(cwd + "_cache.html") or + file_mtime(b + "templates/jk.tmpl") > file.mtime(cwd + "_cache.html") + file.mtime(b + "content.html") > file.mtime(cwd + "_cache.html") then + return 1 + else + return 0 + end Numbers again: @@ -132,15 +140,19 @@ Don't forget: Webserver are built to send out static content, that is what they The index.cml for this looks like: :: - output.content-type text/html + output_content_type = "text/html" + + cwd = request["CWD"] - output.include head.html - output.include menu.html - output.include spacer.html - output.include db-content.html - output.include spacer2.html - output.include news.html - output.include footer.html + output_include = { cwd + "head.html", + cwd + "menu.html", + cwd + "spacer.html", + cwd + "db-content.html", + cwd + "spacer2.html", + cwd + "news.html", + cwd + "footer.html" } + + return 0 Now we get about 10000 req/s instead of 600 req/s. @@ -149,8 +161,66 @@ Options :cml.extension: the file extension that is bound to the cml-module +:cml.memcache-hosts: + hosts for the memcache.* functions +:cml.memcache-namespace: + (not used yet) Language ======== -... will come later ... +The language used for CML is provided by `LUA `_. + +Additionally to the functions provided by lua mod_cml provides: :: + + tables: + + request + - REQUEST_URI + - SCRIPT_NAME + - SCRIPT_FILENAME + - DOCUMENT_ROOT + - PATH_INFO + - CWD + - BASEURI + + get + - parameters from the query-string + + functions: + string md5(string) + number file_mtime(string) + string memcache_get_string(string) + number memcache_get_long(string) + boolean memcache_exists(string) + + +What ever your script does, it has to return either 0 or 1 for ``cache-hit`` or ``cache-miss``. +It case a error occures check the error-log, the user will get a error 500. If you don't like +the standard error-page use ``server.errorfile-prefix``. + +Examples +======== + +Using the memcache-udf for MySQL we can do: :: + + output_contenttype = "text/html" + output_include = { "cache-hit.html" } + + trigger_handler = "generate.php" + + if get["page"] == memcache_get_string("123") then + return 0 + else + return 1 + end + +In MySQL you do: :: + + SELECT memcache_set("127.0.0.1:11211", "123", "12"); + +or to retrieve a value: + + SELECT memcache_get("127.0.0.1:11211", "123"); + +You can get the mysql udf at `jan's mysql page `_. diff --git a/src/Makefile.am b/src/Makefile.am index be69b28d..8372859b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -69,9 +69,9 @@ common_libadd = endif lib_LTLIBRARIES += mod_cml.la -mod_cml_la_SOURCES = mod_cml.c mod_cml_funcs.c mod_cml_logic.c +mod_cml_la_SOURCES = mod_cml.c mod_cml_lua.c mod_cml_funcs.c mod_cml_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined -mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) +mod_cml_la_LIBADD = $(MEMCACHE_LIB) $(common_libadd) $(LUA_LIB) lib_LTLIBRARIES += mod_trigger_b4_dl.la mod_trigger_b4_dl_la_SOURCES = mod_trigger_b4_dl.c diff --git a/src/mod_cml.c b/src/mod_cml.c index a9e60169..89bfb862 100644 --- a/src/mod_cml.c +++ b/src/mod_cml.c @@ -28,11 +28,6 @@ INIT_FUNC(mod_cml_init) { p->session_id = buffer_init(); p->trigger_handler = buffer_init(); - p->eval = buffer_array_init(); - p->trigger_if = buffer_array_init(); - p->output_include = buffer_array_init(); - p->params = tnode_val_array_init(); - return p; } @@ -63,12 +58,6 @@ FREE_FUNC(mod_cml_free) { free(p->config_storage); } - tnode_val_array_free(p->params); - - buffer_array_free(p->eval); - buffer_array_free(p->trigger_if); - buffer_array_free(p->output_include); - buffer_free(p->trigger_handler); buffer_free(p->session_id); buffer_free(p->basedir); @@ -103,6 +92,9 @@ SETDEFAULTS_FUNC(mod_cml_set_defaults) { s->ext = buffer_init(); s->mc_hosts = array_init(); s->mc_namespace = buffer_init(); +#if defined(HAVE_MEMCACHE_H) + s->mc = NULL; +#endif cv[0].destination = s->ext; cv[1].destination = s->mc_hosts; @@ -322,6 +314,7 @@ URIHANDLER_FUNC(mod_cml_is_handled) { buffer *fn = con->physical.path; plugin_data *p = p_d; size_t i; + int ret; if (fn->used == 0) return HANDLER_ERROR; @@ -332,17 +325,11 @@ URIHANDLER_FUNC(mod_cml_is_handled) { mod_cml_patch_connection(srv, con, p, CONST_BUF_LEN(patch)); } - buffer_array_reset(p->output_include); - buffer_array_reset(p->eval); - buffer_array_reset(p->trigger_if); - buffer_reset(p->basedir); buffer_reset(p->baseurl); buffer_reset(p->session_id); buffer_reset(p->trigger_handler); - tnode_val_array_reset(p->params); - if (buffer_is_empty(p->conf.ext)) return HANDLER_GO_ON; ct_len = p->conf.ext->used - 1; @@ -382,7 +369,9 @@ URIHANDLER_FUNC(mod_cml_is_handled) { cache_get_session_id(srv, con, p); - switch(cache_parse(srv, con, p, fn)) { + ret = cache_parse_lua(srv, con, p, fn); + + switch(ret) { case -1: /* error */ if (con->conf.log_request_handling) { diff --git a/src/mod_cml.h b/src/mod_cml.h index 2ecd493b..9b098770 100644 --- a/src/mod_cml.h +++ b/src/mod_cml.h @@ -6,6 +6,7 @@ #include "response.h" #include "stream.h" +#include "plugin.h" #if defined(HAVE_MEMCACHE_H) #include @@ -13,39 +14,6 @@ #define plugin_data mod_cache_plugin_data -typedef enum { UNSET, PART, TIMES, MINUS, PLUS, OR, AND, GT, LT, GE, LE, EQ, NE } tnode_op_t; - -typedef enum { T_NODE_VALUE_UNSET, T_NODE_VALUE_LONG, T_NODE_VALUE_STRING } tnode_val_t; - -typedef struct { - tnode_val_t type; - - union { - buffer *str; - long lon; - } data; -} tnode_val; - -#define VAL_LONG(x) x->value.data.lon -#define VAL_STRING(x) x->value.data.str - -#define IS_LONG(x) ((x->op == UNSET) && (x->value.type == T_NODE_VALUE_LONG)) -#define IS_STRING(x) ((x->op == UNSET) && (x->value.type == T_NODE_VALUE_STRING)) - -typedef struct tnode { - tnode_val value; - tnode_op_t op; - - struct tnode *l, *r; -} tnode; - -typedef struct { - tnode_val **ptr; - - size_t size; - size_t used; -} tnode_val_array; - typedef struct { buffer *ext; @@ -66,40 +34,11 @@ typedef struct { buffer *session_id; - buffer_array *eval; - buffer_array *trigger_if; - buffer_array *output_include; - - tnode_val_array *params; - plugin_config **config_storage; plugin_config conf; } plugin_data; -typedef struct { - char *name; - size_t params; - int (*func)(server *srv, connection *con, plugin_data *p, tnode *result); -} cache_trigger_functions; - -int cache_parse_parameters(server *srv, connection *con, plugin_data *p, const char *params, size_t param_len, tnode_val_array *res); -int cache_parse(server *srv, connection *con, plugin_data *p, buffer *fn); -int tnode_prepare_long(tnode *t); -int tnode_prepare_string(tnode *t); - -tnode_val_array *tnode_val_array_init(); -void tnode_val_array_free(tnode_val_array *tva); -void tnode_val_array_reset(tnode_val_array *tva); - -#define CACHE_FUNC_PROTO(x) int x(server *srv, connection *con, plugin_data *p, tnode *result) - -CACHE_FUNC_PROTO(f_unix_time_now); -CACHE_FUNC_PROTO(f_file_mtime); -CACHE_FUNC_PROTO(f_memcache_exists); -CACHE_FUNC_PROTO(f_memcache_get_string); -CACHE_FUNC_PROTO(f_memcache_get_long); -CACHE_FUNC_PROTO(f_http_request_get_param); -CACHE_FUNC_PROTO(f_crypto_md5); +int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn); #endif diff --git a/src/mod_cml_funcs.c b/src/mod_cml_funcs.c index 44fa973c..d5c7f320 100644 --- a/src/mod_cml_funcs.c +++ b/src/mod_cml_funcs.c @@ -14,6 +14,7 @@ #include "response.h" #include "mod_cml.h" +#include "mod_cml_funcs.h" #ifdef USE_OPENSSL # include @@ -33,259 +34,174 @@ typedef char HASHHEX[HASHHEXLEN+1]; #endif #define OUT -CACHE_FUNC_PROTO(f_unix_time_now) { - UNUSED(srv); - UNUSED(con); - UNUSED(p); - - VAL_LONG(result) = srv->cur_ts; - - return 0; -} +#ifdef HAVE_LUA_H -CACHE_FUNC_PROTO(f_file_mtime) { - buffer *b; - struct stat st; +int f_crypto_md5(lua_State *L) { + MD5_CTX Md5Ctx; + HASH HA1; + buffer b; + char hex[33]; + int n = lua_gettop(L); - UNUSED(con); + b.ptr = hex; + b.used = 0; + b.size = sizeof(hex); - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_file_mtime: I need a string:", - p->params->ptr[0]->type); - - return -1; + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); } - b = buffer_init(); - - /* build filename */ - buffer_copy_string_buffer(b, p->basedir); - buffer_append_string_buffer(b, p->params->ptr[0]->data.str); - - if (-1 == stat(b->ptr, &st)) { - log_error_write(srv, __FILE__, __LINE__, "sbs", - "trigger.if file.mtime():", b, strerror(errno)); - - buffer_free(b); - return -1; + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); } - buffer_free(b); - tnode_prepare_long(result); - VAL_LONG(result) = st.st_mtime; + MD5_Init(&Md5Ctx); + MD5_Update(&Md5Ctx, (unsigned char *)lua_tostring(L, 1), lua_strlen(L, 1)); + MD5_Final(HA1, &Md5Ctx); - return 0; -} - -int split_query_string(server *srv, connection *con, array *vals) { - size_t key_start = 0, key_end = 0, - value_start = 0; - size_t is_key = 1; - size_t i; - - for (i = 0; i < con->uri.query->used; i++) { - switch(con->uri.query->ptr[i]) { - case '=': - if (is_key) { - key_end = i - 1; - value_start = i + 1; - - is_key = 0; - } - - break; - case '&': - case '\0': /* fin symbol */ - if (!is_key) { - data_string *ds; - - /* we need at least a = since the last & */ - - if (NULL == (ds = (data_string *)array_get_unused_element(vals, TYPE_STRING))) { - ds = data_string_init(); - } - - buffer_copy_string_len(ds->key, con->uri.query->ptr + key_start, key_end - key_start); - buffer_copy_string_len(ds->value, con->uri.query->ptr + value_start, i - value_start); - - array_insert_unique(vals, (data_unset *)ds); - } - - key_start = i + 1; - value_start = 0; - is_key = 1; - break; - } - } + buffer_copy_string_hex(&b, (char *)HA1, 16); + + lua_pushstring(L, b.ptr); - return 0; + return 1; } -CACHE_FUNC_PROTO(f_http_request_get_param) { - array *qry_str; - data_string *ds; - - /* fetch data from the con-> request query string */ - - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_http_request_get_param: I need a string:", - p->params->ptr[0]->type); - - return -1; - } - - qry_str = array_init(); - - split_query_string(srv, con, qry_str); - - tnode_prepare_string(result); +int f_file_mtime(lua_State *L) { + struct stat st; + int n = lua_gettop(L); - if (NULL == (ds = (data_string *)array_get_element(qry_str, p->params->ptr[0]->data.str->ptr))) { - - buffer_copy_string(VAL_STRING(result), ""); - - array_free(qry_str); - - return 0; + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); } - buffer_copy_string_buffer(VAL_STRING(result), ds->value); - - array_free(qry_str); - - return 0; -} - -CACHE_FUNC_PROTO(f_crypto_md5) { - MD5_CTX Md5Ctx; - HASH HA1; - - /* fetch data from the con-> request query string */ - - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "crypto.md5: I need a string:", - p->params->ptr[0]->type); - - return -1; + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); } - MD5_Init(&Md5Ctx); - MD5_Update(&Md5Ctx, (unsigned char *)p->params->ptr[0]->data.str->ptr, p->params->ptr[0]->data.str->used - 1); - MD5_Final(HA1, &Md5Ctx); + if (-1 == stat(lua_tostring(L, 1), &st)) { + lua_pushstring(L, "stat failed"); + lua_error(L); + } - tnode_prepare_string(result); - buffer_copy_string_hex(VAL_STRING(result), (char *)HA1, 16); + lua_pushnumber(L, st.st_mtime); - return 0; + return 1; } #ifdef HAVE_MEMCACHE_H -CACHE_FUNC_PROTO(f_memcache_exists) { +int f_memcache_exists(lua_State *L) { char *r; + int n = lua_gettop(L); + struct memcache *mc; - UNUSED(con); - - if (!p->conf.mc) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_exists: no memcache.hosts set:", - p->params->ptr[0]->type); - return -1; + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); } - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_exists: I need a string:", - p->params->ptr[0]->type); - - return -1; + mc = lua_touserdata(L, lua_upvalueindex(1)); + + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); } - tnode_prepare_long(result); + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); + } - if (NULL == (r = mc_aget(p->conf.mc, - CONST_BUF_LEN(p->params->ptr[0]->data.str)))) { + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { - VAL_LONG(result) = 0; - return 0; + lua_pushboolean(L, 0); + return 1; } free(r); - VAL_LONG(result) = 1; - - return 0; + lua_pushboolean(L, 1); + return 1; } -CACHE_FUNC_PROTO(f_memcache_get_string) { +int f_memcache_get_string(lua_State *L) { char *r; + int n = lua_gettop(L); + + struct memcache *mc; + + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); + } + + mc = lua_touserdata(L, lua_upvalueindex(1)); - UNUSED(con); - if (!p->conf.mc) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_get_string: no memcache.hosts set:", - p->params->ptr[0]->type); - return -1; + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); } - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_get_string: I need a string:", - p->params->ptr[0]->type); - return -1; + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); } - if (NULL == (r = mc_aget(p->conf.mc, - p->params->ptr[0]->data.str->ptr, p->params->ptr[0]->data.str->used - 1))) { - log_error_write(srv, __FILE__, __LINE__, "sb", - "f_memcache_get_string: couldn't find:", - p->params->ptr[0]->data.str); - return -1; + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { + lua_pushnil(L); + return 1; } - tnode_prepare_string(result); - buffer_copy_string(VAL_STRING(result), r); + + lua_pushstring(L, r); free(r); - return 0; + return 1; } -CACHE_FUNC_PROTO(f_memcache_get_long) { +int f_memcache_get_long(lua_State *L) { char *r; + int n = lua_gettop(L); - UNUSED(con); + struct memcache *mc; - if (!p->conf.mc) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_get_long: no memcache.hosts set:", - p->params->ptr[0]->type); - return -1; + if (!lua_islightuserdata(L, lua_upvalueindex(1))) { + lua_pushstring(L, "where is my userdata ?"); + lua_error(L); } - if (p->params->ptr[0]->type != T_NODE_VALUE_STRING) { - log_error_write(srv, __FILE__, __LINE__, "sd", - "f_memcache_get_long: I need a string:", - p->params->ptr[0]->type); - return -1; + mc = lua_touserdata(L, lua_upvalueindex(1)); + + + if (n != 1) { + lua_pushstring(L, "expected one argument"); + lua_error(L); + } + + if (!lua_isstring(L, 1)) { + lua_pushstring(L, "argument has to be a string"); + lua_error(L); } - if (NULL == (r = mc_aget(p->conf.mc, - CONST_BUF_LEN(p->params->ptr[0]->data.str)))) { - log_error_write(srv, __FILE__, __LINE__, "sb", - "f_memcache_get_long: couldn't find:", - p->params->ptr[0]->data.str); - return -1; + if (NULL == (r = mc_aget(mc, + lua_tostring(L, 1), lua_strlen(L, 1)))) { + lua_pushnil(L); + return 1; } - tnode_prepare_long(result); - VAL_LONG(result) = strtol(r, NULL, 10); + lua_pushnumber(L, strtol(r, NULL, 10)); free(r); - return 0; + return 1; } #endif + +#endif