Annotation of embedaddon/lighttpd/doc/outdated/magnet.txt, revision 1.1.1.1

1.1       misho       1: {{{
                      2: #!rst
                      3: ==============
                      4: a power-magnet
                      5: ==============
                      6: 
                      7: ------------------
                      8: Module: mod_magnet
                      9: ------------------
                     10: 
                     11: 
                     12: 
                     13: .. contents:: Table of Contents
                     14: 
                     15: Requirements
                     16: ============
                     17: 
                     18: :Version: lighttpd 1.4.12 or higher
                     19: :Packages: lua >= 5.1
                     20: 
                     21: Overview
                     22: ========
                     23: 
                     24: mod_magnet is a module to control the request handling in lighty. 
                     25: 
                     26: .. note::
                     27: 
                     28:   Keep in mind that the magnet is executed in the core of lighty. EVERY long-running operation is blocking 
                     29:   ALL connections in the server. You are warned. For time-consuming or blocking scripts use mod_fastcgi and friends.
                     30: 
                     31: For performance reasons mod_magnet caches the compiled script. For each script-run the script itself is checked for 
                     32: freshness and recompile if neccesary.
                     33: 
                     34: 
                     35: Installation
                     36: ============
                     37: 
                     38: mod_magnet needs a lighty which is compiled with the lua-support ( --with-lua). Lua 5.1 or higher are required by
                     39: the module. Use "--with-lua=lua5.1" to install on Debian and friends. ::
                     40: 
                     41:   server.modules = ( ..., "mod_magnet", ... )
                     42: 
                     43: Options
                     44: =======
                     45: 
                     46: mod_magnet can attract a request in several stages in the request-handling. 
                     47: 
                     48: * either at the same level as mod_rewrite, before any parsing of the URL is done
                     49: * or at a later stage, when the doc-root is known and the physical-path is already setup
                     50: 
                     51: It depends on the purpose of the script which stage you want to intercept. Usually you want to use
                     52: the 2nd stage where the physical-path which relates to your request is known. At this level you
                     53: can run checks against lighty.env["physical.path"].
                     54: 
                     55: ::
                     56: 
                     57:   magnet.attract-raw-url-to = ( ... )
                     58:   magnet.attract-physical-path-to = ( ... )
                     59: 
                     60: You can define multiple scripts when separated by a semicolon. The scripts are executed in the specified 
                     61: order. If one of them a returning a status-code, the following scripts will not be executed.
                     62: 
                     63: Tables
                     64: ======
                     65: 
                     66: Most of the interaction between between mod_magnet and lighty is done through tables. Tables in lua are hashes (Perl), dictionaries (Java), arrays (PHP), ...
                     67: 
                     68: Request-Environment
                     69: -------------------
                     70: 
                     71: Lighttpd has its internal variables which are exported as read/write to the magnet. 
                     72: 
                     73: If "http://example.org/search.php?q=lighty" is requested this results in a request like ::
                     74: 
                     75:   GET /search.php?q=lighty HTTP/1.1
                     76:   Host: example.org
                     77: 
                     78: When you are using ``attract-raw-url-to`` you can access the following variables:
                     79: 
                     80: * parts of the request-line
                     81: 
                     82:  * lighty.env["request.uri"] = "/search.php?q=lighty"
                     83: 
                     84: * HTTP request-headers
                     85: 
                     86:   * lighty.request["Host"] = "example.org"
                     87: 
                     88: Later in the request-handling, the URL is splitted, cleaned up and turned into a physical path name:
                     89: 
                     90: * parts of the URI
                     91: 
                     92:  * lighty.env["uri.path"] = "/search.php"
                     93:  * lighty.env["uri.path-raw"] = "/search.php"
                     94:  * lighty.env["uri.scheme"] = "http"
                     95:  * lighty.env["uri.authority"] = "example.org"
                     96:  * lighty.env["uri.query"] = "q=lighty"
                     97: 
                     98: * filenames, pathnames
                     99: 
                    100:  * lighty.env["physical.path"] = "/my-docroot/search.php"
                    101:  * lighty.env["physical.rel-path"] = "/search.php"
                    102:  * lighty.env["physical.doc-root"] = "/my-docroot"
                    103: 
                    104: All of them are readable, not all of the are writable (or don't have an effect if you write to them). 
                    105: 
                    106: As a start, you might want to use those variables for writing: ::
                    107: 
                    108:   -- 1. simple rewriting is done via the request.uri
                    109:   lighty.env["request.uri"] = ... 
                    110:   return lighty.RESTART_REQUEST
                    111: 
                    112:   -- 2. changing the physical-path
                    113:   lighty.env["physical.path"] = ...
                    114: 
                    115:   -- 3. changing the query-string
                    116:   lighty.env["uri.query"] = ...
                    117: 
                    118: Response Headers
                    119: ----------------
                    120: 
                    121: If you want to set a response header for your request, you can add a field to the lighty.header[] table: ::
                    122: 
                    123:   lighty.header["Content-Type"] = "text/html"
                    124: 
                    125: Sending Content
                    126: ===============
                    127: 
                    128: You can generate your own content and send it out to the clients. ::
                    129: 
                    130:   lighty.content = { "<pre>", { filename = "/etc/passwd" }, "</pre>" }
                    131:   lighty.header["Content-Type"] = "text/html"
                    132: 
                    133:   return 200
                    134: 
                    135: 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.
                    136: 
                    137: * Strings
                    138: 
                    139:   * are included as is
                    140: 
                    141: * Tables
                    142: 
                    143:   * filename = "<absolute-path>" is required
                    144:   * offset = <number> [default: 0]
                    145:   * length = <number> [default: size of the file - offset]
                    146: 
                    147: Internally lighty will use the sendfile() call to send out the static files at full speed.
                    148: 
                    149: Status Codes
                    150: ============
                    151: 
                    152: You might have seen it already in other examples: In case you are handling the request completly in the magnet you
                    153: can return your own status-codes. Examples are: Redirected, Input Validation, ... ::
                    154: 
                    155:   if (lighty.env["uri.scheme"] == "http") then
                    156:     lighty.header["Location"] = "https://" .. lighty.env["uri.authority"] .. lighty.env["request.uri"]
                    157:     return 302
                    158:   end
                    159: 
                    160: You every number above and equal to 100 is taken as final status code and finishes the request. No other modules are 
                    161: executed after this return.
                    162: 
                    163: A special return-code is lighty.RESTART_REQUEST (currently equal to 99) which is usually used in combination with 
                    164: changing the request.uri in a rewrite. It restarts the splitting of the request-uri again.
                    165: 
                    166: If you return nothing (or nil) the request-handling just continues.
                    167: 
                    168: Debugging
                    169: =========
                    170: 
                    171: To easy debugging we overloaded the print()-function in lua and redirect the output of print() to the error-log. ::
                    172: 
                    173:   print("Host: " .. lighty.request["Host"])
                    174:   print("Request-URI: " .. lighty.env["request.uri"])
                    175: 
                    176: 
                    177: Examples
                    178: ========
                    179: 
                    180: Sending text-files as HTML
                    181: --------------------------
                    182: 
                    183: This is a bit simplistic, but it illustrates the idea: Take a text-file and cover it in a <pre> tag.
                    184: 
                    185: Config-file ::
                    186: 
                    187:   magnet.attract-physical-path-to = server.docroot + "/readme.lua"
                    188: 
                    189: readme.lua ::
                    190: 
                    191:   lighty.content = { "<pre>", { filename = "/README" }, "</pre>" }
                    192:   lighty.header["Content-Type"] = "text/html"
                    193:   
                    194:   return 200
                    195: 
                    196: Maintainance pages
                    197: ------------------
                    198: 
                    199: Your side might be on maintainance from time to time. Instead of shutting down the server confusing all
                    200: users, you can just send a maintainance page.
                    201: 
                    202: Config-file ::
                    203: 
                    204:   magnet.attract-physical-path-to = server.docroot + "/maintainance.lua"
                    205: 
                    206: maintainance.lua ::
                    207: 
                    208:   require "lfs"
                    209: 
                    210:   if (nil == lfs.attributes(lighty.env["physical.doc-root"] .. "/maintainance.html")) then
                    211:     lighty.content = ( lighty.env["physical.doc-root"] .. "/maintainance.html" )
                    212: 
                    213:     lighty.header["Content-Type"] = "text/html"
                    214: 
                    215:     return 200
                    216:   end
                    217: 
                    218: mod_flv_streaming
                    219: -----------------
                    220: 
                    221: Config-file ::
                    222: 
                    223:   magnet.attract-physical-path-to = server.docroot + "/flv-streaming.lua"
                    224: 
                    225: flv-streaming.lua::
                    226: 
                    227:   if (lighty.env["uri.query"]) then
                    228:     -- split the query-string
                    229:     get = {}
                    230:     for k, v in string.gmatch(lighty.env["uri.query"], "(%w+)=(%w+)") do
                    231:       get[k] = v
                    232:     end
                    233: 
                    234:     if (get["start"]) then
                    235:       -- missing: check if start is numeric and positive
                    236: 
                    237:       -- send te FLV header + a seek into the file
                    238:       lighty.content = { "FLV\x1\x1\0\0\0\x9\0\0\0\x9", 
                    239:          { filename = lighty.env["physical.path"], offset = get["start"] } }
                    240:       lighty.header["Content-Type"] = "video/x-flv"
                    241: 
                    242:       return 200
                    243:     end
                    244:   end
                    245: 
                    246:   
                    247: selecting a random file from a directory
                    248: ----------------------------------------
                    249: 
                    250: Say, you want to send a random file (ad-content) from a directory. 
                    251: 
                    252: To simplify the code and to improve the performance we define:
                    253: 
                    254: * all images have the same format (e.g. image/png)
                    255: * all images use increasing numbers starting from 1
                    256: * a special index-file names the highest number
                    257: 
                    258: Config ::
                    259: 
                    260:   server.modules += ( "mod_magnet" )
                    261:   magnet.attract-physical-path-to = "random.lua"
                    262: 
                    263: random.lua ::
                    264: 
                    265:   dir = lighty.env["physical.path"]
                    266: 
                    267:   f = assert(io.open(dir .. "/index", "r"))
                    268:   maxndx = f:read("*all")
                    269:   f:close()
                    270: 
                    271:   ndx = math.random(maxndx)
                    272: 
                    273:   lighty.content = { { filename = dir .. "/" .. ndx }}
                    274:   lighty.header["Content-Type"] = "image/png"
                    275: 
                    276:   return 200
                    277: 
                    278: denying illegal character sequences in the URL
                    279: ----------------------------------------------
                    280: 
                    281: Instead of implementing mod_security, you might just want to apply filters on the content
                    282: and deny special sequences that look like SQL injection. 
                    283: 
                    284: A common injection is using UNION to extend a query with another SELECT query.
                    285: 
                    286: ::
                    287: 
                    288:   if (string.find(lighty.env["request.uri"], "UNION%s")) then
                    289:     return 400
                    290:   end
                    291: 
                    292: Traffic Quotas
                    293: --------------
                    294: 
                    295: If you only allow your virtual hosts a certain amount for traffic each month and want to 
                    296: disable them if the traffic is reached, perhaps this helps: ::
                    297: 
                    298:   host_blacklist = { ["www.example.org"] = 0 }
                    299: 
                    300:   if (host_blacklist[lighty.request["Host"]]) then
                    301:     return 404
                    302:   end
                    303: 
                    304: Just add the hosts you want to blacklist into the blacklist table in the shown way.
                    305: 
                    306: Complex rewrites
                    307: ----------------
                    308: 
                    309: If you want to implement caching on your document-root and only want to regenerate 
                    310: content if the requested file doesn't exist, you can attract the physical.path: ::
                    311: 
                    312:   magnet.attract-physical-path-to = ( server.document-root + "/rewrite.lua" )
                    313: 
                    314: rewrite.lua ::
                    315: 
                    316:   require "lfs"
                    317: 
                    318:   attr = lfs.attributes(lighty.env["physical.path"])
                    319: 
                    320:   if (not attr) then
                    321:     -- we couldn't stat() the file for some reason
                    322:     -- let the backend generate it
                    323: 
                    324:     lighty.env["uri.path"] = "/dispatch.fcgi"
                    325:     lighty.env["physical.rel-path"] = lighty.env["uri.path"]
                    326:     lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]
                    327:   fi
                    328: 
                    329: luafilesystem
                    330: +++++++++++++
                    331: 
                    332: We are requiring the lua-module 'lfs' (http://www.keplerproject.org/luafilesystem/). 
                    333: 
                    334: I had to compile lfs myself for lua-5.1 which required a minor patch as compat-5.1 is not needed::
                    335: 
                    336:   $ wget http://luaforge.net/frs/download.php/1487/luafilesystem-1.2.tar.gz
                    337:   $ wget http://www.lighttpd.net/download/luafilesystem-1.2-lua51.diff
                    338:   $ gzip -cd luafilesystem-1.2.tar.gz | tar xf -
                    339:   $ cd luafilesystem-1.2
                    340:   $ patch -ls -p1 < ../luafilesystem-1.2-lua51.diff
                    341:   $ make install
                    342: 
                    343: It will install lfs.so into /usr/lib/lua/5.1/ which is where lua expects the extensions on my system.
                    344: 
                    345: SuSE and Gentoo are known to have their own lfs packages and don't require a compile.
                    346: 
                    347: Usertracking
                    348: ------------
                    349: 
                    350: ... or how to store data globally in the script-context:
                    351: 
                    352: Each script has its own script-context. When the script is started it only contains the lua-functions
                    353: and the special lighty.* name-space. If you want to save data between script runs, you can use the global-script
                    354: context:
                    355: 
                    356: ::
                    357: 
                    358:   if (nil == _G["usertrack"]) then
                    359:     _G["usertrack"] = {}
                    360:   end
                    361:   if (nil == _G["usertrack"][lighty.request["Cookie"]]) then
                    362:     _G["usertrack"][lighty.request["Cookie"]]
                    363:   else 
                    364:     _G["usertrack"][lighty.request["Cookie"]] = _G["usertrack"][lighty.request["Cookie"]] + 1
                    365:   end
                    366: 
                    367:   print _G["usertrack"][lighty.request["Cookie"]]
                    368: 
                    369: The global-context is per script. If you update the script without restarting the server, the context will still be maintained.
                    370: 
                    371: Counters
                    372: --------
                    373: 
                    374: mod_status support a global statistics page and mod_magnet allows to add and update values in the status page:
                    375: 
                    376: Config ::
                    377: 
                    378:   status.statistics-url = "/server-counters"
                    379:   magnet.attract-raw-url-to = server.docroot + "/counter.lua"
                    380: 
                    381: counter.lua ::
                    382: 
                    383:   lighty.status["core.connections"] = lighty.status["core.connections"] + 1
                    384: 
                    385: Result::
                    386: 
                    387:   core.connections: 7
                    388:   fastcgi.backend.php-foo.0.connected: 0
                    389:   fastcgi.backend.php-foo.0.died: 0
                    390:   fastcgi.backend.php-foo.0.disabled: 0
                    391:   fastcgi.backend.php-foo.0.load: 0
                    392:   fastcgi.backend.php-foo.0.overloaded: 0
                    393:   fastcgi.backend.php-foo.1.connected: 0
                    394:   fastcgi.backend.php-foo.1.died: 0
                    395:   fastcgi.backend.php-foo.1.disabled: 0
                    396:   fastcgi.backend.php-foo.1.load: 0
                    397:   fastcgi.backend.php-foo.1.overloaded: 0
                    398:   fastcgi.backend.php-foo.load: 0
                    399: 
                    400: Porting mod_cml scripts
                    401: -----------------------
                    402: 
                    403: mod_cml got replaced by mod_magnet.
                    404: 
                    405: A CACHE_HIT in mod_cml::
                    406:  
                    407:   output_include = { "file1", "file2" } 
                    408: 
                    409:   return CACHE_HIT
                    410: 
                    411: becomes::
                    412: 
                    413:   content = { { filename = "/path/to/file1" }, { filename = "/path/to/file2"} }
                    414: 
                    415:   return 200
                    416: 
                    417: while a CACHE_MISS like (CML) ::
                    418: 
                    419:   trigger_handler = "/index.php"
                    420: 
                    421:   return CACHE_MISS
                    422: 
                    423: becomes (magnet) ::
                    424: 
                    425:   lighty.env["request.uri"] = "/index.php"
                    426: 
                    427:   return lighty.RESTART_REQUEST
                    428: 
                    429: }}}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>