123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- {{{
- #!rst
- ==============
- a power-magnet
- ==============
- ------------------
- Module: mod_magnet
- ------------------
- .. contents:: Table of Contents
- Requirements
- ============
- :Version: lighttpd 1.4.12 or higher
- :Packages: lua >= 5.1
- Overview
- ========
- mod_magnet is a module to control the request handling in lighty.
- .. note::
- Keep in mind that the magnet is executed in the core of lighty. EVERY long-running operation is blocking
- ALL connections in the server. You are warned. For time-consuming or blocking scripts use mod_fastcgi and friends.
- For performance reasons mod_magnet caches the compiled script. For each script-run the script itself is checked for
- freshness and recompile if necessary.
- Installation
- ============
- mod_magnet needs a lighty which is compiled with the lua-support ( --with-lua). Lua 5.1 or higher are required by
- the module. Use "--with-lua=lua5.1" to install on Debian and friends. ::
- server.modules = ( ..., "mod_magnet", ... )
- Options
- =======
- mod_magnet can attract a request in several stages in the request-handling.
- * either at the same level as mod_rewrite, before any parsing of the URL is done
- * or at a later stage, when the doc-root is known and the physical-path is already setup
- It depends on the purpose of the script which stage you want to intercept. Usually you want to use
- the 2nd stage where the physical-path which relates to your request is known. At this level you
- can run checks against lighty.env["physical.path"].
- ::
- magnet.attract-raw-url-to = ( ... )
- magnet.attract-physical-path-to = ( ... )
- You can define multiple scripts when separated by a semicolon. The scripts are executed in the specified
- order. If one of them a returning a status-code, the following scripts will not be executed.
- Tables
- ======
- Most of the interaction between between mod_magnet and lighty is done through tables. Tables in lua are hashes (Perl), dictionaries (Java), arrays (PHP), ...
- Request-Environment
- -------------------
- Lighttpd has its internal variables which are exported as read/write to the magnet.
- If "http://example.org/search.php?q=lighty" is requested this results in a request like ::
- GET /search.php?q=lighty HTTP/1.1
- Host: example.org
- When you are using ``attract-raw-url-to`` you can access the following variables:
- * parts of the request-line
- * lighty.env["request.uri"] = "/search.php?q=lighty"
- * HTTP request-headers
- * lighty.request["Host"] = "example.org"
- Later in the request-handling, the URL is split, cleaned up and turned into a physical path name:
- * parts of the URI
- * lighty.env["uri.path"] = "/search.php"
- * lighty.env["uri.path-raw"] = "/search.php"
- * lighty.env["uri.scheme"] = "http"
- * lighty.env["uri.authority"] = "example.org"
- * lighty.env["uri.query"] = "q=lighty"
- * filenames, pathnames
- * lighty.env["physical.path"] = "/my-docroot/search.php"
- * lighty.env["physical.rel-path"] = "/search.php"
- * lighty.env["physical.doc-root"] = "/my-docroot"
- All of them are readable, not all of the are writable (or don't have an effect if you write to them).
- As a start, you might want to use those variables for writing: ::
- -- 1. simple rewriting is done via the request.uri
- lighty.env["request.uri"] = ...
- return lighty.RESTART_REQUEST
- -- 2. changing the physical-path
- lighty.env["physical.path"] = ...
- -- 3. changing the query-string
- lighty.env["uri.query"] = ...
- Response Headers
- ----------------
- If you want to set a response header for your request, you can add a field to the lighty.header[] table: ::
- lighty.header["Content-Type"] = "text/html"
- Sending Content
- ===============
- You can generate your own content and send it out to the clients. ::
- lighty.content = { "<pre>", { filename = "/etc/passwd" }, "</pre>" }
- lighty.header["Content-Type"] = "text/html"
- return 200
- The lighty.content[] table is executed when the script is finished. The elements of the array are processed left to right and the elements can either be a string or a table. Strings are included AS IS into the output of the request.
- * Strings
- * are included as is
- * Tables
- * filename = "<absolute-path>" is required
- * offset = <number> [default: 0]
- * length = <number> [default: size of the file - offset]
- Internally lighty will use the sendfile() call to send out the static files at full speed.
- Status Codes
- ============
- You might have seen it already in other examples: In case you are handling the request completely in the magnet you
- can return your own status-codes. Examples are: Redirected, Input Validation, ... ::
- if (lighty.env["uri.scheme"] == "http") then
- lighty.header["Location"] = "https://" .. lighty.env["uri.authority"] .. lighty.env["request.uri"]
- return 302
- end
- You every number above and equal to 100 is taken as final status code and finishes the request. No other modules are
- executed after this return.
- A special return-code is lighty.RESTART_REQUEST (currently equal to 99) which is usually used in combination with
- changing the request.uri in a rewrite. It restarts the splitting of the request-uri again.
- If you return nothing (or nil) the request-handling just continues.
- Debugging
- =========
- To easy debugging we overloaded the print()-function in lua and redirect the output of print() to the error-log. ::
- print("Host: " .. lighty.request["Host"])
- print("Request-URI: " .. lighty.env["request.uri"])
- Examples
- ========
- 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 ::
- magnet.attract-physical-path-to = server.docroot + "/readme.lua"
- readme.lua ::
- lighty.content = { "<pre>", { filename = "/README" }, "</pre>" }
- lighty.header["Content-Type"] = "text/html"
-
- return 200
- Maintenance pages
- ------------------
- Your side might be on maintenance from time to time. Instead of shutting down the server confusing all
- users, you can just send a maintenance page.
- Config-file ::
- magnet.attract-physical-path-to = server.docroot + "/maintenance.lua"
- maintenance.lua ::
- require "lfs"
- if (nil == lfs.attributes(lighty.env["physical.doc-root"] .. "/maintenance.html")) then
- lighty.content = ( lighty.env["physical.doc-root"] .. "/maintenance.html" )
- lighty.header["Content-Type"] = "text/html"
- return 200
- end
- mod_flv_streaming
- -----------------
- Config-file ::
- magnet.attract-physical-path-to = server.docroot + "/flv-streaming.lua"
- flv-streaming.lua::
- 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
- if (get["start"]) then
- -- missing: check if start is numeric and positive
- -- send the FLV header + a seek into the file
- lighty.content = { "FLV\x1\x1\0\0\0\x9\0\0\0\x9",
- { filename = lighty.env["physical.path"], offset = get["start"] } }
- lighty.header["Content-Type"] = "video/x-flv"
- return 200
- end
- end
-
- 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 ::
- server.modules += ( "mod_magnet" )
- magnet.attract-physical-path-to = "random.lua"
- random.lua ::
- 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
- 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.
- ::
- if (string.find(lighty.env["request.uri"], "UNION%s")) then
- return 400
- end
- 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: ::
- host_blacklist = { ["www.example.org"] = 0 }
- if (host_blacklist[lighty.request["Host"]]) then
- return 404
- end
- 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: ::
- magnet.attract-physical-path-to = ( server.document-root + "/rewrite.lua" )
- rewrite.lua ::
- require "lfs"
- attr = lfs.attributes(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"]
- fi
- luafilesystem
- +++++++++++++
- We are requiring the lua-module 'lfs' (http://www.keplerproject.org/luafilesystem/).
- I had to compile lfs myself for lua-5.1 which required a minor patch as compat-5.1 is not needed::
- $ wget http://luaforge.net/frs/download.php/1487/luafilesystem-1.2.tar.gz
- $ wget http://www.lighttpd.net/download/luafilesystem-1.2-lua51.diff
- $ gzip -cd luafilesystem-1.2.tar.gz | tar xf -
- $ cd luafilesystem-1.2
- $ patch -ls -p1 < ../luafilesystem-1.2-lua51.diff
- $ make install
- It will install lfs.so into /usr/lib/lua/5.1/ which is where lua expects the extensions on my system.
- SuSE and Gentoo are known to have their own lfs packages and don't require a compile.
- Usertracking
- ------------
- ... 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:
- ::
- 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"]]
- The global-context is per script. If you update the script without restarting the server, the context will still be maintained.
- Counters
- --------
- mod_status support a global statistics page and mod_magnet allows to add and update values in the status page:
- Config ::
- status.statistics-url = "/server-counters"
- magnet.attract-raw-url-to = server.docroot + "/counter.lua"
- counter.lua ::
- lighty.status["core.connections"] = lighty.status["core.connections"] + 1
- Result::
- core.connections: 7
- fastcgi.backend.php-foo.0.connected: 0
- fastcgi.backend.php-foo.0.died: 0
- fastcgi.backend.php-foo.0.disabled: 0
- fastcgi.backend.php-foo.0.load: 0
- fastcgi.backend.php-foo.0.overloaded: 0
- fastcgi.backend.php-foo.1.connected: 0
- fastcgi.backend.php-foo.1.died: 0
- fastcgi.backend.php-foo.1.disabled: 0
- fastcgi.backend.php-foo.1.load: 0
- fastcgi.backend.php-foo.1.overloaded: 0
- fastcgi.backend.php-foo.load: 0
- Porting mod_cml scripts
- -----------------------
- mod_cml got replaced by mod_magnet.
- A CACHE_HIT in mod_cml::
-
- output_include = { "file1", "file2" }
- return CACHE_HIT
- becomes::
- content = { { filename = "/path/to/file1" }, { filename = "/path/to/file2"} }
- return 200
- while a CACHE_MISS like (CML) ::
- trigger_handler = "/index.php"
- return CACHE_MISS
- becomes (magnet) ::
- lighty.env["request.uri"] = "/index.php"
- return lighty.RESTART_REQUEST
- }}}
|