You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
6.7 KiB
Plaintext
262 lines
6.7 KiB
Plaintext
18 years ago
|
=========================
|
||
|
CML (Cache Meta Language)
|
||
|
=========================
|
||
|
|
||
|
---------------
|
||
|
Module: mod_cml
|
||
|
---------------
|
||
|
|
||
|
:Author: Jan Kneschke
|
||
|
:Date: $Date: 2004/11/03 22:26:05 $
|
||
|
:Revision: $Revision: 1.2 $
|
||
|
|
||
|
:abstract:
|
||
18 years ago
|
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 years ago
|
|
||
18 years ago
|
.. meta::
|
||
18 years ago
|
:keywords: lighttpd, cml, lua
|
||
![]()
17 years ago
|
|
||
18 years ago
|
.. contents:: Table of Contents
|
||
|
|
||
|
Description
|
||
|
===========
|
||
|
|
||
|
CML (Cache Meta Language) wants to solves several problems:
|
||
|
|
||
|
* dynamic content needs caching to perform
|
||
|
* checking if the content is dirty inside of the application is usually more expensive than sending out the cached data
|
||
|
* a dynamic page is usually fragmented and the fragments have different livetimes
|
||
|
* the different fragements can be cached independently
|
||
|
|
||
|
Cache Decision
|
||
|
--------------
|
||
|
|
||
![]()
17 years ago
|
A simple example should show how to a content caching the very simple way in PHP.
|
||
18 years ago
|
|
||
|
jan.kneschke.de has a very simple design:
|
||
|
|
||
|
* the layout is taken from a template in templates/jk.tmpl
|
||
|
* the menu is generated from a menu.csv file
|
||
|
* the content is coming from files on the local directory named content-1, content-2 and so on
|
||
|
|
||
![]()
17 years ago
|
The page content is static as long non of the those tree items changes. A change in the layout
|
||
18 years ago
|
is affecting all pages, a change of menu.csv too, a change of content-x file only affects the
|
||
|
cached page itself.
|
||
|
|
||
|
If we model this in PHP we get: ::
|
||
|
|
||
|
<?php
|
||
|
|
||
|
## ... fetch all content-* files into $content
|
||
|
$cachefile = "/cache/dir/to/cached-content";
|
||
|
|
||
|
function is_cachable($content, $cachefile) {
|
||
|
if (!file_exists($cachefile)) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
$cachemtime = filemtime($cachefile);
|
||
|
}
|
||
|
|
||
|
foreach($content as $k => $v) {
|
||
![]()
17 years ago
|
if (isset($v["file"]) &&
|
||
18 years ago
|
filemtime($v["file"]) > $cachemtime) {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (filemtime("/menu/menu.csv") > $cachemtime) {
|
||
|
return 0;
|
||
|
}
|
||
|
if (filemtime("/templates/jk.tmpl") > $cachemtime) {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
![]()
17 years ago
|
|
||
18 years ago
|
if (is_cachable(...), $cachefile) {
|
||
|
readfile($cachefile);
|
||
|
exit();
|
||
|
} else {
|
||
|
# generate content and write it to $cachefile
|
||
|
}
|
||
|
?>
|
||
|
|
||
|
Quite simple. No magic involved. If the one of the files is new than the cached
|
||
![]()
17 years ago
|
content, the content is dirty and has to be regenerated.
|
||
18 years ago
|
|
||
|
Now let take a look at the numbers:
|
||
|
|
||
|
* 150 req/s for a Cache-Hit
|
||
|
* 100 req/s for a Cache-Miss
|
||
|
|
||
|
As you can see the increase is not as good as it could be. The main reason as the overhead
|
||
|
of the PHP interpreter to start up (a byte-code cache has been used here).
|
||
|
|
||
|
Moving these decisions out of the PHP script into a server module will remove the need
|
||
![]()
17 years ago
|
to start PHP for a cache-hit.
|
||
18 years ago
|
|
||
![]()
17 years ago
|
To transform this example into a CML you need 'index.cml' in the list of indexfiles
|
||
18 years ago
|
and the following index.cml file: ::
|
||
|
|
||
17 years ago
|
output_contenttype = "text/html"
|
||
18 years ago
|
|
||
18 years ago
|
b = request["DOCUMENT_ROOT"]
|
||
|
cwd = request["CWD"]
|
||
18 years ago
|
|
||
18 years ago
|
output_include = { b .. "_cache.html" }
|
||
18 years ago
|
|
||
|
trigger_handler = "index.php"
|
||
|
|
||
18 years ago
|
if file_mtime(b .. "../lib/php/menu.csv") > file_mtime(cwd .. "_cache.html") or
|
||
18 years ago
|
file_mtime(b .. "templates/jk.tmpl") > file_mtime(cwd .. "_cache.html") or
|
||
|
file_mtime(b .. "content.html") > file_mtime(cwd .. "_cache.html") then
|
||
18 years ago
|
return CACHE_MISS
|
||
![]()
17 years ago
|
else
|
||
18 years ago
|
return CACHE_HIT
|
||
18 years ago
|
end
|
||
18 years ago
|
|
||
|
Numbers again:
|
||
|
|
||
|
* 4900 req/s for Cache-Hit
|
||
|
* 100 req/s for Cache-Miss
|
||
|
|
||
|
Content Assembling
|
||
|
------------------
|
||
|
|
||
|
Sometimes the different fragment are already generated externally. You have to cat them together: ::
|
||
|
|
||
|
<?php
|
||
|
readfile("head.html");
|
||
|
readfile("menu.html");
|
||
|
readfile("spacer.html");
|
||
|
readfile("db-content.html");
|
||
|
readfile("spacer2.html");
|
||
|
readfile("news.html");
|
||
|
readfile("footer.html");
|
||
![]()
17 years ago
|
?>
|
||
18 years ago
|
|
||
![]()
17 years ago
|
We we can do the same several times faster directly in the webserver.
|
||
18 years ago
|
|
||
|
Don't forget: Webserver are built to send out static content, that is what they can do best.
|
||
|
|
||
|
The index.cml for this looks like: ::
|
||
|
|
||
17 years ago
|
output_contenttype = "text/html"
|
||
![]()
17 years ago
|
|
||
18 years ago
|
cwd = request["CWD"]
|
||
![]()
17 years ago
|
|
||
|
output_include = { cwd .. "head.html",
|
||
18 years ago
|
cwd .. "menu.html",
|
||
|
cwd .. "spacer.html",
|
||
|
cwd .. "db-content.html",
|
||
|
cwd .. "spacer2.html",
|
||
|
cwd .. "news.html",
|
||
|
cwd .. "footer.html" }
|
||
![]()
17 years ago
|
|
||
18 years ago
|
return CACHE_HIT
|
||
18 years ago
|
|
||
|
Now we get about 10000 req/s instead of 600 req/s.
|
||
|
|
||
17 years ago
|
Power Magnet
|
||
|
------------
|
||
|
|
||
|
Next to all the features about Cache Decisions CML can do more. Starting
|
||
|
with lighttpd 1.4.9 a power-magnet was added which attracts each request
|
||
![]()
17 years ago
|
and allows you to manipulate the request for your needs.
|
||
17 years ago
|
|
||
|
We want to display a maintainance page by putting a file in a specified
|
||
|
place:
|
||
|
|
||
|
We enable the power magnet: ::
|
||
|
|
||
|
cml.power-magnet = "/home/www/power-magnet.cml"
|
||
|
|
||
|
and create /home/www/power-magnet.cml with: ::
|
||
|
|
||
|
dr = request["DOCUMENT_ROOT"]
|
||
|
|
||
|
if file_isreg(dr .. 'maintainance.html') then
|
||
|
output_include = { 'maintainance.html' }
|
||
|
return CACHE_HIT
|
||
|
end
|
||
|
|
||
|
return CACHE_MISS
|
||
|
|
||
|
For each requested file the /home/www/power-magnet.cml is executed which
|
||
|
checks if maintainance.html exists in the docroot and displays it
|
||
![]()
17 years ago
|
instead of handling the usual request.
|
||
17 years ago
|
|
||
|
Another example, create thumbnail for requested image and serve it instead
|
||
|
of sending the big image: ::
|
||
|
|
||
|
## image-url is /album/baltic_winter_2005.jpg
|
||
|
## no params -> 640x480 is served
|
||
|
## /album/baltic_winter_2005.jpg/orig for full size
|
||
|
## /album/baltic_winter_2005.jpg/thumb for thumbnail
|
||
|
|
||
|
dr = request["DOCUMENT_ROOT"]
|
||
|
sn = request["SCRIPT_NAME"]
|
||
|
|
||
![]()
17 years ago
|
## to be continued :) ...
|
||
17 years ago
|
|
||
|
trigger_handler = '/gen_image.php'
|
||
|
|
||
|
return CACHE_MISS
|
||
|
|
||
|
|
||
18 years ago
|
Installation
|
||
|
============
|
||
|
|
||
18 years ago
|
You need `lua <http://www.lua.org/>`_ and should install `libmemcache-1.3.x <http://people.freebsd.org/~seanc/libmemcache/>`_ and have to configure lighttpd with: ::
|
||
18 years ago
|
|
||
|
./configure ... --with-lua --with-memcache
|
||
|
|
||
|
To use the plugin you have to load it: ::
|
||
|
|
||
|
server.modules = ( ..., "mod_cml", ... )
|
||
|
|
||
18 years ago
|
Options
|
||
|
=======
|
||
|
|
||
![]()
17 years ago
|
:cml.extension:
|
||
18 years ago
|
the file extension that is bound to the cml-module
|
||
18 years ago
|
:cml.memcache-hosts:
|
||
|
hosts for the memcache.* functions
|
||
|
:cml.memcache-namespace:
|
||
|
(not used yet)
|
||
17 years ago
|
:cml.power-magnet:
|
||
|
a cml file that is executed for each request
|
||
18 years ago
|
|
||
|
Language
|
||
|
========
|
||
|
|
||
![]()
17 years ago
|
The language used for CML is provided by `LUA <http://www.lua.org/>`_.
|
||
18 years ago
|
|
||
|
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)
|
||
![]()
17 years ago
|
boolean memcache_exists(string)
|
||
18 years ago
|
|
||
|
|
||
![]()
17 years ago
|
What ever your script does, it has to return either 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
|
||
18 years ago
|
the standard error-page use ``server.errorfile-prefix``.
|
||
|
|