Project

General

Profile

AbsoLUAtion » History » Version 9

Version 8 (icy, 2008-11-05 17:28) → Version 9/30 (nitrox, 2008-11-06 12:29)

h1. AbsoLUAtion - The powerful combo of Lighttpd + Lua

{{>toc}}

We want to build a central ressource for Lighttpd + Lua as this is one of the biggest advantages Lighttpd has over other webservers. It's useful, handy, simple and sometimes a quite powerful combo which gives you some additional flexibility and offers you solutions to small and big problems other httpds can't solve!

Again we hope you - the users of lighty - support this page by contributing links, code-snippets or simply offer your lua scripts with small descriptions of what they do and how it helps lighty to do stuff you want it to do.

h1. What's needed for this to work?

* A [[DevelSubversion|recent]] version of Lighttpd
* [[lighttpd:Docs:ModMagnet|mod_magnet]]
* Lua (v5.0 or better v5.1, your distro´s should have this) http://www.lua.org

h1. How to get it up and running

[[lighttpd:Docs:ModMagnet]] <pre>
./autogen.sh
./configure --your-normal-options-here \
--with-lua=lua #will check for lua or lua5.x on debian
./make
./make check #or VERBOSE=1 make check for detailed results
./make install
</pre>


h1. Links

Link collection (Description -> Link).

* darix is maintaining the cleanurl.lua at http://pixel.global-banlist.de/
* Jippi is maintaining the bundle.lua at http://www.cakephp.nu/faster-page-loads-bundle-your-css-and-javascript-lighttpd-mod_magnet-lua
* http://www.sitepoint.com/blogs/2007/04/10/faster-page-loads-bundle-your-css-and-javascript/
* Google CodeSearch is great for looking Lua examples. http://www.google.com/codesearch start your queries with "lang:lua"
Bundle css + js files to save some hits and not break browsers parallel download: "lighttpd+mod_magnet+lua":http://www.cakephp.nu/faster-page-loads-bundle-your-css-and-javascript-lighttpd-mod_magnet-lua

h1. Code-Snippets

*overwrite default mime-type/content-type*

Add "magnet.attract-physical-path-to = ( "/path-to/change-ctype.lua" )" to lighttpd.conf and save the following as "change-ctype.lua"
<pre>
if (string.match(lighty.env["physical.rel-path"], ".swf")) then
lighty.header["Content-Type"] = "text/html"
</pre>

*Sending text-files as HTML*

This is a bit simplistic, but it illustrates the idea: Take a text-file and cover it in a "< pre >" tag.

Config-file

<pre>
magnet.attract-physical-path-to = (server.docroot + "/readme.lua")
</pre>

readme.lua

<pre>
lighty.content = { "<pre>", { filename = "/README" }, "</pre>" }
lighty.header["Content-Type"] = "text/html"

return 200
</pre>

*simple maintenance script*

You need three files, maint.up, maint.down and maint.html.
maint.html holds a simple html-page of what you want to display to your users while in maintenance-mode.

Add "magnet.attract-physical-path-to = ( "/path-to-your/maint.lua" )" to your lighttpd.conf, best is global section or within a host-section of your config, e.g. a board/forum/wiki you know a maintenance-mode is needed from time to time. If you want to switch to maintenance-mode, just copy maint.down to maint.lua in your "/path-to-your/" location, and lighty will display your maint.html to all users - without restarting anything - this can be done on-the-fly. Work is done and all is up again? Copy maint.up to maint.lua in your "/path-to-your/" location. Whats maint.up doing? Nothing, just going on with normal file serving :-)

maint.up - all is up, user will see normal pages

<pre>
-- This is empty, nothing to do.
</pre>

maint.down - lighty will show the maintenance page -> maint.html

<pre>
-- lighty.header["X-Maintenance-Mode"] = "1"
-- uncomment the above if you want to add the header
lighty.content = { { filename = "/path-to-your/maint.html" } }
lighty.header["Content-Type"] = "text/html"
return 503
-- or return 200 if you want
</pre>

*mod_flv_streaming*


Config-file

<pre>
magnet.attract-physical-path-to = (server.docroot + "/flv-streaming.lua")
</pre>

flv-streaming.lua

<pre>
if (lighty.env["uri.query"]) then
-- split the query-string
get = {}
for k, v in string.gmatch(lighty.env["uri.query"], "(%w+)=(%w+)") do
get[k] = v
end

header=""
if (get and get["start"]) then
start = tonumber(get["start"])
else
start=0
end

-- send te FLV header only when seeking + a seek into the file
if (start ~= nil and start > 0) then
header="FLV\1\1\0\0\0\9\0\0\0\9"
end

lighty.content = { header ,
{ filename = lighty.env["physical.path"], offset = start } }
lighty.header["Content-Type"] = "video/x-flv"

return 200

end
</pre>

You can also use a backend like php to use your own authorization or stuff like mod_secdl. Just activate x-rewrite in the backend configuration and use a header like

<pre>
header("X-Rewrite-URI: flvstreaming?start=" . $start . "&path=" . $path);
</pre>

The request is restarted and in the lua, you can catch the non-existing uri with the following code (wrap it between the example below)::

<pre>
if (string.find(lighty.env["uri.path"],"/flvstreaming") then
<flv streaming lua code>
end
</pre>

In the future, there will be a new magnet for response headers, maybe you can give your own headers like::

<pre>
header("X-StreamMyFlv: $path");
</pre>

to lua and use the header data as parameter for the streaming.

*selecting a random file from a directory*

Say, you want to send a random file (ad-content) from a directory.

To simplify the code and to improve the performance we define:

* all images have the same format (e.g. image/png)
* all images use increasing numbers starting from 1
* a special index-file names the highest number

Config

<pre>
server.modules += ( "mod_magnet" )
magnet.attract-physical-path-to = ("random.lua")
</pre>

random.lua

<pre>
dir = lighty.env["physical.path"]

f = assert(io.open(dir .. "/index", "r"))
maxndx = f:read("*all")
f:close()

ndx = math.random(maxndx)

lighty.content = { { filename = dir .. "/" .. ndx }}
lighty.header["Content-Type"] = "image/png"

return 200
</pre>

*denying illegal character sequences in the URL*

Instead of implementing mod_security, you might just want to apply filters on the content and deny special sequences that look like SQL injection.

A common injection is using UNION to extend a query with another SELECT query.

<pre>
if (string.find(lighty.env["request.uri"], "UNION%s")) then
return 400
end
</pre>

*Traffic Quotas*

If you only allow your virtual hosts a certain amount for traffic each month and want to disable them if the traffic is reached, perhaps this helps:

<pre>
host_blacklist = { ["www.example.org"] = 0 }

if (host_blacklist[lighty.request["Host"]]) then
return 404
end
</pre>

Just add the hosts you want to blacklist into the blacklist table in the shown way.

*Complex rewrites*

If you want to implement caching on your document-root and only want to regenerate content if the requested file doesn't exist, you can attract the physical.path:

<pre>
magnet.attract-physical-path-to = ( server.document-root + "/rewrite.lua" )
</pre>

rewrite.lua

<pre>
attr = lighty.stat(lighty.env["physical.path"])

if (not attr) then
-- we couldn't stat() the file for some reason
-- let the backend generate it

lighty.env["uri.path"] = "/dispatch.fcgi"
lighty.env["physical.rel-path"] = lighty.env["uri.path"]
lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]
end
</pre>

*Extension rewrites*

If you want to hide your file extensions (like .php) you can attract the physical.path:

<pre>
magnet.attract-physical-path-to = ( server.document-root + "/rewrite.lua" )
</pre>

rewrite.lua

<pre>
attr = lighty.stat(lighty.env["physical.path"] .. ".php")

if (attr) then
lighty.env["uri.path"] = lighty.env["uri.path"] .. ".php"
lighty.env["physical.rel-path"] = lighty.env["uri.path"]
lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]
end
</pre>

*User tracking*

... or how to store data globally in the script-context:

Each script has its own script-context. When the script is started it only contains the lua-functions and the special lighty.* name-space. If you want to save data between script runs, you can use the global-script context:

<pre>
if (nil == _G["usertrack"]) then
_G["usertrack"] = {}
end
if (nil == _G["usertrack"][lighty.request["Cookie"]]) then
_G["usertrack"][lighty.request["Cookie"]]
else
_G["usertrack"][lighty.request["Cookie"]] = _G["usertrack"][lighty.request["Cookie"]] + 1
end

print _G["usertrack"][lighty.request["Cookie"]]
</pre>

The global-context is per script. If you update the script without restarting the server, the context will still be maintained.

h1. Small Helpers

...

h1. other solutions

*external-static*

I´ve seen this nice solution somewhere where they host some files locally on their machines. If popularity gets to high, files are too big or for whatever reasons the files are moved to i think it was amazon´s S3 or akamai for faster serving or to cope with high traffic. You still can use your hostname, urls, collect stats from your logs - your users are just redirected with a 302 to the files they ask for.

Request -> check for local copy -> 302 (if not stored locally) -> let users download from a big pipe

Add the following to your lighttpd.conf:
<pre>
$HTTP["url"] =~ "^/static/[^/]+[.]gif([?].*)?$" { #match the files you want this to work for
magnet.attract-physical-path-to = ( "/path-to-your/external-static.lua" )
}
</pre>

Save the following to external-static.lua:
<pre>
local filename = lighty.env["physical.path"]
local stat = lighty.stat( filename )
if not stat then
local static_name = string.match( filename, "static/([^/]+)$" )
lighty.header["Location"] = "http://<new-location-with-big-pipes>/" .. static_name
return 302
end
</pre>