1 =========================
2 CML (Cache Meta Language)
3 =========================
10 :Date: $Date: 2004/11/03 22:26:05 $
11 :Revision: $Revision: 1.2 $
14 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.
17 :keywords: lighttpd, cml, lua
19 .. contents:: Table of Contents
24 CML (Cache Meta Language) wants to solves several problems:
26 * dynamic content needs caching to perform
27 * checking if the content is dirty inside of the application is usually more expensive than sending out the cached data
28 * a dynamic page is usually fragmented and the fragments have different livetimes
29 * the different fragements can be cached independently
34 A simple example should show how to a content caching the very simple way in PHP.
36 jan.kneschke.de has a very simple design:
38 * the layout is taken from a template in templates/jk.tmpl
39 * the menu is generated from a menu.csv file
40 * the content is coming from files on the local directory named content-1, content-2 and so on
42 The page content is static as long non of the those tree items changes. A change in the layout
43 is affecting all pages, a change of menu.csv too, a change of content-x file only affects the
46 If we model this in PHP we get: ::
50 ## ... fetch all content-* files into $content
51 $cachefile = "/cache/dir/to/cached-content";
53 function is_cachable($content, $cachefile) {
54 if (!file_exists($cachefile)) {
57 $cachemtime = filemtime($cachefile);
60 foreach($content as $k => $v) {
61 if (isset($v["file"]) &&
62 filemtime($v["file"]) > $cachemtime) {
67 if (filemtime("/menu/menu.csv") > $cachemtime) {
70 if (filemtime("/templates/jk.tmpl") > $cachemtime) {
75 if (is_cachable(...), $cachefile) {
79 # generate content and write it to $cachefile
83 Quite simple. No magic involved. If the one of the files is new than the cached
84 content, the content is dirty and has to be regenerated.
86 Now let take a look at the numbers:
88 * 150 req/s for a Cache-Hit
89 * 100 req/s for a Cache-Miss
91 As you can see the increase is not as good as it could be. The main reason as the overhead
92 of the PHP interpreter to start up (a byte-code cache has been used here).
94 Moving these decisions out of the PHP script into a server module will remove the need
95 to start PHP for a cache-hit.
97 To transform this example into a CML you need 'index.cml' in the list of indexfiles
98 and the following index.cml file: ::
100 output_contenttype = "text/html"
102 b = request["DOCUMENT_ROOT"]
105 output_include = { b .. "_cache.html" }
107 trigger_handler = "index.php"
109 if file_mtime(b .. "../lib/php/menu.csv") > file_mtime(cwd .. "_cache.html") or
110 file_mtime(b .. "templates/jk.tmpl") > file_mtime(cwd .. "_cache.html") or
111 file_mtime(b .. "content.html") > file_mtime(cwd .. "_cache.html") then
119 * 4900 req/s for Cache-Hit
120 * 100 req/s for Cache-Miss
125 Sometimes the different fragment are already generated externally. You have to cat them together: ::
128 readfile("head.html");
129 readfile("menu.html");
130 readfile("spacer.html");
131 readfile("db-content.html");
132 readfile("spacer2.html");
133 readfile("news.html");
134 readfile("footer.html");
137 We we can do the same several times faster directly in the webserver.
139 Don't forget: Webserver are built to send out static content, that is what they can do best.
141 The index.cml for this looks like: ::
143 output_contenttype = "text/html"
147 output_include = { cwd .. "head.html",
149 cwd .. "spacer.html",
150 cwd .. "db-content.html",
151 cwd .. "spacer2.html",
153 cwd .. "footer.html" }
157 Now we get about 10000 req/s instead of 600 req/s.
162 Next to all the features about Cache Decisions CML can do more. Starting
163 with lighttpd 1.4.9 a power-magnet was added which attracts each request
164 and allows you to manipulate the request for your needs.
166 We want to display a maintainance page by putting a file in a specified
169 We enable the power magnet: ::
171 cml.power-magnet = "/home/www/power-magnet.cml"
173 and create /home/www/power-magnet.cml with: ::
175 dr = request["DOCUMENT_ROOT"]
177 if file_isreg(dr .. 'maintainance.html') then
178 output_include = { 'maintainance.html' }
184 For each requested file the /home/www/power-magnet.cml is executed which
185 checks if maintainance.html exists in the docroot and displays it
186 instead of handling the usual request.
188 Another example, create thumbnail for requested image and serve it instead
189 of sending the big image: ::
191 ## image-url is /album/baltic_winter_2005.jpg
192 ## no params -> 640x480 is served
193 ## /album/baltic_winter_2005.jpg/orig for full size
194 ## /album/baltic_winter_2005.jpg/thumb for thumbnail
196 dr = request["DOCUMENT_ROOT"]
197 sn = request["SCRIPT_NAME"]
199 ## to be continued :) ...
201 trigger_handler = '/gen_image.php'
209 You need `lua <http://www.lua.org/>`_ and should install `memcached <http://www.memcached.org>`_ and have to configure lighttpd with: ::
211 ./configure ... --with-lua --with-memcached
213 To use the plugin you have to load it: ::
215 server.modules = ( ..., "mod_cml", ... )
221 the file extension that is bound to the cml-module
223 hosts for the memcache.* functions
224 :cml.memcache-namespace:
227 a cml file that is executed for each request
232 The language used for CML is provided by `LUA <http://www.lua.org/>`_.
234 Additionally to the functions provided by lua mod_cml provides: ::
248 - parameters from the query-string
252 number file_mtime(string)
253 string memcache_get_string(string)
254 number memcache_get_long(string)
255 boolean memcache_exists(string)
258 What ever your script does, it has to return either CACHE_HIT or CACHE_MISS.
259 It case a error occures check the error-log, the user will get a error 500. If you don't like
260 the standard error-page use ``server.errorfile-prefix``.