Mod magnet » History » Revision 36
« Previous |
Revision 36/119
(diff)
| Next »
jan, 2007-04-10 16:54
{{{
#!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 = ( [absolute path to lua script] )
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, Ruby), dictionaries (Java, Python), associative arrays (PHP), ...
- lighty.request[]
- lighty.env[]
- physical.path
- physical.rel-path
- physical.doc-root
- uri.path (the URI without the query-string)
- uri.path-raw
- uri.scheme (http or https)
- uri.authority (the server-name)
- uri.query (the URI after the ? )
- request.method (e.g. GET)
- request.uri (uri after rewrite)
- request.orig-uri (before rewrite)
- request.protocol (e.g. HTTP/1.0)
- lighty.header[]
- lighty.status[]
- lighty.content[]
lighty.env[]
------------
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 splitted, 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"] = ...
lighty.header[]
---------------
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"
lighty.status[]
---------------
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
Exported Functions ==================
mod-magnet exports a few functions to the script:
- print (writes to the error-log)
- lighty.stat()
print()
-------
print() overwrites the lua-default version and sends the content to the errorlog.
lighty.stat()
-------------
lighty.stat() checks the existence of a file/dir/socket and returns the stat() information for it.
It is using lighty's internal stat-cache.::
- array lighty.stat(path)
- @param path (string) absolute path to stat()
- @returns array or nil on error
*/
If the call was successful you'll be able to query the following fields from the array:
- is_file
- is_dir
- is_char
- is_block
- is_socket
- is_link
- is_fifo
- st_mtime
- st_ctime
- st_atime
- st_uid
- st_gui
- st_size
- st_ino
Sending Content ===============
You can generate your own content and send it out to the clients. ::
lighty.content = { "", { filename = "/etc/passwd" }, "
" }
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 completly 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
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 Maintainance pages ------------------ Your site might be on maintainance from time to time. Instead of shutting down the server confusing all users, you can just send a maintainance page. Config-file :: magnet.attract-physical-path-to = (server.docroot + "/maintainance.lua") maintainance.lua :: if not (nil == lighty.stat(lighty.env["physical.doc-root"] .. "/maintainance.html")) then lighty.content = { { filename = lighty.env["physical.doc-root"] .. "/maintainance.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 te 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 :: 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 Extension rewrites ------------------ If you want to hide your file extensions (like .php) you can attract the physical.path: :: magnet.attract-physical-path-to = ( server.document-root + "/rewrite.lua" ) rewrite.lua :: 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 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: :: 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. 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 }}}
Updated by jan over 17 years ago · 36 revisions