magnet.txt 12 KB

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