Search
j0ke.net Open Build Service
>
Projects
>
ha
>
lighttpd-snapshot
> mod_magnet.txt
Sign Up
|
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File mod_magnet.txt of Package lighttpd-snapshot
{{{ #!rst ============== a power-magnet ============== ------------------ Module: mod_magnet ------------------ .. contents:: Table of Contents Requirements ============ :Version: lighttpd 1.4.12 and higher :Packages: lua >= 5.0 Overview ======== mod_magnet is a module to control the request handling in lighty. .. note:: Currently this page only describes the final syntax and some part might not work with the current svn-code. .. 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 neccesary. Installation ============ mod_magnet needs a lighty which is compiled with the lua-support (--with-lua). Lua 5.0 or higher are required by the module. :: 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 = ... 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 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"] = ... 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 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 <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 Maintainance pages ------------------ Your side 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 :: require "lfs" if (nil == lfs.attributes(lighty.env["physical.doc-root"] .. "/maintainance.html")) then lighty.content = ( 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.get["start"]) { lighty.content = { "FLV\x1\x1\0\0\0\x9\0\0\0\x9", { filename = lighty.env["physical.path"], offset = lighty.get["start"] } } lighty.header["Content-Type"] = "video/x-flv" return 200 } 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 Complex rewrites ---------------- If you want to rewrite a URL depending on a complex condition, here you go: :: require "lfs" attr = lfs.attributes(lighty.env["physical.path"]) if (attr) then -- file exists, send it -- we might also check for dir, file, socket, ... lighty.content = { { filename = lighty.env["physical.path"] } } return 200 fi -- path doesn't exist ... rewrite it lighty.env["request.uri"] = "/dispatch.fcgi" return lighty.RESTART_REQUEST In case you can't install the luafilesystem module, it might perhaps be enough to use the io.open() call to see if the file exists: :: f = io.open(lighty.env["physical.path"], "r") if (f) then f:close() lighty.content = { { filename = lighty.env["physical.path"] } } return 200 end -- io.open returns nil + a error-msg on the stack pop(1) -- path doesn't exist ... rewrite it lighty.env["request.uri"] = "/dispatch.fcgi" return lighty.RESTART_REQUEST 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 }}}