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>