Return to magnet.txt CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / lighttpd / doc / outdated |
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: }}}