Project

General

Profile

Mod magnet » History » Revision 115

Revision 114 (gstrauss, 2023-03-02 05:00) → Revision 115/119 (flynn, 2023-10-17 13:02)

 
 {{>toc}} 

 h1. lighttpd request manipulation using Lua 

 *Module: mod_magnet* 

 h2. Overview 

 mod_magnet enables programmatic manipulation of lighttpd request handling via Lua (programming language) scripts. 

 [[Docs_ModMagnet#Examples|mod_magnet lua examples]] demonstrate the power and flexibility of a few lines of Lua with lighttpd.    mod_magnet allows you to do more complex URL rewrites and caching than you might otherwise be able to do in lighttpd.conf, including [[DevelProblemAndSolution#htaccess-like-functionality|.htaccess-like functionality in lighttpd]]. 

 While Lua is a very powerful programming language, learning basic string manipulation and plugging a lua script into lighttpd should be relatively straightforward for anyone with basic scripting experience in another scripting language (Python, PHP, Ruby, etc).    Please review the lua examples here and give it a try!    Questions?    Post in the "lighttpd Forums":https://redmine.lighttpd.net/projects/lighttpd/boards 

 Please note: mod_magnet is aimed at request manipulation such as url-rewriting and is not meant to be a general replacement for your entire scripting environment.    mod_magnet executes lua scripts in the lighttpd core which makes lua scripting very fast in lighttpd, but any blocking operation (e.g. I/O to an external database) will pause the lighttpd server for all other requests.    For time-consuming or blocking lua scripts, please run your script outside of lighttpd, using [[Docs_ModFastCGI|mod_fastcgi]], [[Docs_ModProxy|mod_proxy]], or other dynamic backend.    For those interested in running time-consuming or blocking lua scripts, one option is a third-party FastCGI daemon such as "luafcgid":https://github.com/SnarkyClark/luafcgid 


 h2. Requirements 

 lighttpd 1.4.12 or higher built @--with-lua@ 
 lua >= 5.1 


 h2. Options 

 mod_magnet can attract a request during one or more stages of request handling.    You may define multiple scripts, separated by commas, for each stage. The scripts are executed in the order specified.    If a script returns a value other than 0 or 1xx, the scripts following will not be executed.    See [[#Return-Codes|Return Codes]] below.    For performance reasons, mod_magnet caches each compiled script.    For each request, the script is checked and if the script has been modified, then the script is reloaded and recompiled.    Script changes take effect upon the next request, without needing to restart lighttpd. 

 @magnet.attract-raw-url-to = (...)@ - URL processing after HTTP request headers are received (and after [[Docs_ModRewrite|mod_rewrite]]) 
 @magnet.attract-physical-path-to = (...)@ - filesystem processing after document root and physical path are set to initial values 
 @magnet.attract-response-start-to = (...)@ - response start after HTTP status is set and before response headers are finalized (since 1.4.56) 

 e.g. @magnet.attract-physical-path-to = ( "/absolute/path/to/script.lua"    )@ 


 h2. Return Codes 

 * If script @return 0@, or returns nothing (or nil), request handling continues. 
 * If script @return lighty.RESTART_REQUEST@ (currently equal to 99), the request is restarted, reprocessing the request-uri.    This is usually used in combination with changing the @["request.uri"]@ attribute in a rewrite. 
 * If script returns 1xx, a 1xx intermediate response is sent, and request handling continues. 
 * If script returns >= 200, the value is used as final HTTP status code and the response is finalized.    No other modules are executed. 

 Example redirecting "http" to "https": (@lighty.*@ accessors are described further below) 
 (e.g. [[HowToRedirectHttpToHttps|How to redirect HTTP requests to HTTPS]] using [[Docs_ModRedirect|mod_redirect]]) 
 <pre> 
   local r = lighty.r 
   local req_attr = r.req_attr 
   if (req_attr["uri.scheme"] == "http") then 
     r.resp_header["Location"] = "https://" .. req_attr["uri.authority"] .. req_attr["request.uri"] 
     return 308 
   end 
 </pre> 


 h2. Detecting Request Stage 

 If the same lua script is configured to run during multiple request stages, the lua script can detect the request stage using @lighty.*@ (@lighty.*@ accessors are described further below) 

 @magnet.attract-raw-url-to@ 
 ** @lighty.r.req_attr["physical.path"] == nil@ 
 ** (since lighttpd 1.4.65) <code>lighty.r.req_item.http_status == 0</code> 
 ** (before lighttpd 1.4.65) <code>lighty.r.req_attr["response.http-status"] == "0"</code> 

 @magnet.attract-physical-path-to@ 
 ** @lighty.r.req_attr["physical.path"] ~= nil@ 
 ** (since lighttpd 1.4.65) <code>lighty.r.req_item.http_status == 0</code> 
 ** (before lighttpd 1.4.65) <code>lighty.r.req_attr["response.http-status"] == "0"</code> 

 @magnet.attract-response-start-to@ (since 1.4.56) 
 ** (since lighttpd 1.4.65) <code>lighty.r.req_item.http_status > 0</code> 
 ** (before lighttpd 1.4.65) <code>lighty.r.req_attr["response.http-status"] > 0</code> 



 h2. Examples 

 &nbsp; 

 * [[ModMagnetExamples|lua examples of lighttpd modules]] 

 * [[Lua_Trigger_Functions|lua trigger functions]] 

 * [[AbsoLUAtion|Abso *lua* tion for additional info, code-snippets, examples ...]] 

 &nbsp; 



 h2. Library Functions 

 mod_magnet exports a few additional functions to the script: 
 * @pairs()@ - extends the default @pairs()@ function (for lua 5.1) 
 * @print()@ - writes to the lighttpd error log; useful for debugging 
 ** e.g. @print("Host: " .. lighty.r.req_header["Host"])@ 
 ** e.g. @print("Request-URI: " .. lighty.r.req_attr["request.uri"])@ 
 * @lighty.stat()@ - @stat()@ file information (prefer: @lighty.c.stat()@) 

 &nbsp; 



 h2. mod_magnet API since lighttpd 1.4.60 

 (The earlier mod_magnet API (above) is still supported, but the newer mod_magnet API (below) should be preferred.) 

 h3. @lighty.r@ request object                                                                       

 (since lighttpd 1.4.60)                                                                             

 |_. @lighty.r@                |_. description | 
 | @lighty.r.req_header[]@     | HTTP request headers | 
 | @lighty.r.req_attr[]@       | HTTP request attributes / components | 
 | @lighty.r.req_item[]@       | HTTP request select internal members (since 1.4.65) | 
 | @lighty.r.req_env[]@        | HTTP request environment variables | 
 | @lighty.r.req_body.*@       | HTTP request body attributes and accessors (since 1.4.65) | 
 | @lighty.r.resp_header[]@    | HTTP response headers | 
 | @lighty.r.resp_body.*@      | HTTP response body attributes and accessors | 


 h5. @lighty.r@ request object modification                                                          
        
 * @lighty.r.req_header[]@ allows get/set of request headers                                         
   If modifications would affect config processing, script should return                             
   @lighty.RESTART_REQUEST@ to have lighttpd restart the modified request.                           
   @lighty.r.req_header[]@ differs from the older API @lighty.env[]@ table,                          
   which (previously) did not permit modification of request headers.                                
   Note: header policy is not applied to values set in @lighty.r.req_header[]@;                      
         Do not set unvalidated, untrusted, or non-normalized values.                                
   Note: if iterating using @pairs()@, do not set request header to blank value                      
         during iteration, or else iteration may end up skipping request headers.                     
                                                                                                   
 * @lighty.r.req_attr[]@ allows get/set of request attributes and is detailed further below.                 
   @lighty.r.req_attr[]@ is the same as the (less clearly named) older API @lighty.env[]@            

 * @lighty.r.req_item[]@ allows (mostly) read-only access to select internal request members 
   and is detailed further below. (since 1.4.65) 
        
 * @lighty.r.req_env[]@ allows get/set of request environment variables                              
   @lighty.r.req_env[]@ is the same as the older API @lighty.req_env[]@                              
   Note: modifications made to standard CGI environment variables                                    
   will be overwritten by backends recreating the CGI environment.                                   
   However, new variables will persist into the env passed to backend scripts.                       

 * @lighty.r.req_body.*@ adds/sets request body content (since 1.4.65) 
 |_. @lighty.r.req_body@            |_. description | 
 | @lighty.r.req_body.len@          | HTTP request body length  
                                    (0 if none; -1 if not yet known; >= 0 if Content-Length sent by client or request body complete) | 
 | @lighty.r.req_body.collect@      | HTTP request body schedule collection (after which request will be restarted and scripts re-run) 
                                    (note: request body offload disables request streaming to backend; defers connecting to backend) 
                                    - returns 1 if request body complete (and then script can call @r.req_body.get@) 
                                    - returns 0 if request body incomplete (and then script should return 0 so script can be re-run later) | 
 | @lighty.r.req_body.get@          | HTTP request body get (NB: reads entire request into memory; check @r.req_body.len@ for sanity) 
                                    (nil if request body incomplete (see @r.req_body.collect@)) | 
 | @lighty.r.req_body.add()@        | HTTP request body add (string or table of strings) 
                                    (should also @r.req_header["Content-Length"] = nil@ (or set to actual value) if used) | 
 | @lighty.r.req_body.set()@        | HTTP request body set (string or table of strings) 
                                    (should also @r.req_header["Content-Length"] = nil@ (or set to actual value) if used) | 
 | @lighty.r.req_body.bytes_in@     | HTTP request body bytes received from client (reset by @r.req_body.set()@) | 
 | @lighty.r.req_body.bytes_out@    | HTTP request body bytes sent to backend (reset by @r.req_body.set()@) | 
                                                                                                   
 * @lighty.r.resp_header[]@ allows get/set of response headers                                       
   (Certain connection-level headers such as Connection and                                          
   Transfer-Encoding are restricted from modification)                                               
   @lighty.r.resp_headers[]@ differs from the older API @lighty.header[]@ table,                     
   which is collected and deferred, being applied after the script exits.                            
   Note: header policy is not applied to values set in @lighty.r.resp_header[]@; 
         Do not set unvalidated, untrusted, or non-normalized values.                                
   To repeated header names, such as Set-Cookie or Link, join with "\r\nNAME:" 
   @lighty.r.resp_header["Link"] = "http://a.com/a.css\r\nLink: http:/b.com/b.js"@                   
                
 * @lighty.r.resp_body.*@ adds/sets response body content                                            
   @lighty.r.resp_body.*@ differs from the older API @lighty.content[]@ table, 
   which is collected and deferred, being applied after the script exits. 
 |_. @lighty.r.resp_body@            |_. description | 
 | @lighty.r.resp_body.len@          | HTTP response body length | 
 | @lighty.r.resp_body.get@          | HTTP response body get (NB: reads entire response into memory; check @r.resp_body.len@ for sanity) (since 1.4.65) | 
 | @lighty.r.resp_body.add()@        | HTTP response body add (string or table) | 
 | @lighty.r.resp_body.set()@        | HTTP response body set (string or table) | 
 | @lighty.r.resp_body.bytes_in@     | HTTP response body bytes queued to send (since 1.4.65) | 
 | @lighty.r.resp_body.bytes_out@    | HTTP response body bytes sent to client (since 1.4.65) | 

 <pre> 
 -- examples 
 local r = lighty.r 
 local resp_header = r.resp_header 
 resp_header["Content-Type"] = "text/html" 
 resp_header["Cache-Control"] = "max-age=0" 
 r.resp_body:set({'bar\n'})    -- equivalent to below 'set' 
 -- (suggested if script run via magnet.attract-response-start-to) 
 resp_header["Content-Length"] = nil 
 -- alternatives 
 r.resp_body.set({'bar\n'})    -- equivalent to above 'set' 
 lighty.r.resp_header["Content-Type"] = "text/html" 
 -- deprecated older syntax (API before lighttpd 1.4.60) (less clearly named) 
 lighty.header["Content-Type"] = "text/html" 
 lighty.content = {'bar\n'} 
 </pre> 


 h5. @lighty.r.req_attr[]@ readable attributes 

 |_. @lighty.r.req_attr[]@       |_. description | 
 | @["uri.scheme"]@              | ("http", "https") | 
 | @["uri.authority"]@           | URI authority or Host request header | 
 | @["uri.path"]@                | url-path without the query-string; url-path is url-decoded | 
 | @["uri.path-raw"]@            | url-path without the query-string; url-path is not url-decoded 
                                 (url-path component from @["request.uri"]@, i.e. without query-string) | 
 | @["uri.query"]@               | query-string; URL part following '?'; query-string is not url-decoded | 
 | | | 
 | @["request.method"]@          | request method (e.g. GET) | 
 | @["request.protocol"]@        | request protocol ("HTTP/1.0", "HTTP/1.1", "HTTP/2.0") | 
 | @["request.uri"]@             | URI after mod_rewrite rules, if any, else same as request.orig-uri | 
 | @["request.orig-uri"]@        | URI before mod_rewrite; original request-uri sent by client | 
 | @["request.path-info"]@       | path-info following url-path | 
 | @["request.server-addr"]@     | server addr | 
 | @["request.server-port"]@     | server port | 
 | @["request.remote-addr"]@     | remote addr | 
 | @["request.remote-port"]@     | remote port | 
 | @["request.server-name"]@     | often the same as @["uri.authority"]@ unless @server.name@ configured in lighttpd.conf | 
 | @["request.stage"]@           | string tag of internal request stage (e.g. for dislay; similar to mod_status) | 
 | | | 
 | @["physical.doc-root"]@       | filesystem document root (original) | 
 | @["physical.basedir"]@        | filesystem document root (same as physical.doc-root unless adjusted by mod_alias, mod_userdir, ...) | 
 | @["physical.path"]@           | filesystem path to request (beginning with physical.basedir) | 
 | @["physical.rel-path"]@       | filesystem path to request (piece appended to physical.basedir) | 
 | | | 
 | @["response.http-status"]@    | HTTP response status (0 if not yet set) 
                                 (DEPRECATED; replace with @lighty.r.req_item.http_status@ since lighttpd 1.4.65) 
                                 (REMOVED in lighttpd 1.4.68) | 
 | @["response.body-length"]@    | HTTP response body length 
                                 (DEPRECATED; replace with @lighty.r.resp_body.len@ since lighttpd 1.4.60) 
                                 (REMOVED in lighttpd 1.4.68) 
                                 (nil unless response body is complete) | 
 | @["response.body"]@           | HTTP response body 
                                 (DEPRECATED; replace with @lighty.r.resp_body.get@ since lighttpd 1.4.65) 
                                 (REMOVED in lighttpd 1.4.68) 
                                 (nil unless response body is complete) 
                                 (copies response in memory; should not be used on very large responses) | 

 A full URI can be reconstructed using components: 
 <pre> 
 local req_attr = lighty.r.req_attr 
 local query_string = req_attr["uri.query"] 
 local url = req_attr["uri.scheme"] 
          .. "://" 
          .. req_attr["uri.authority"] 
          .. req_attr["uri.path-raw"] 
          .. (query_string and ("?" .. query_string) or "") 
 </pre> 
 More examples: 
 <pre> 
 -- curl "https://example.org/search.php?q=lighty" 
 -- results in a request like: 
 --     GET /search.php?q=lighty HTTP/1.1 
 --     Host: example.org 

 local r = lighty.r 
 local req_attr = r.req_attr 
 local req_header = r.req_header 

 -- scripts run via magnet.attract-raw-url-to can access the following variables: 

 -- parts of the request-line 
 req_attr["request.uri"] == "/search.php?q=lighty" 
 -- HTTP request-headers 
 req_header["Host"] == "example.org" 
 -- parts of the URI 
 req_attr["uri.path"] == "/search.php" 
 req_attr["uri.path-raw"] == "/search.php" 
 req_attr["uri.scheme"] == "https" 
 req_attr["uri.authority"] == "example.org" 
 req_attr["uri.query"] == "q=lighty" 

 -- Later in the request-handling, the URL is split, cleaned up, and turned into a physical path name 
 -- scripts run via magnet.attract-physical-path-to can access the following variables: 

 -- filenames, pathnames 
 req_attr["physical.path"] == "/my-docroot/search.php" 
 req_attr["physical.rel-path"] == "/search.php" 
 req_attr["physical.doc-root"] == "/my-docroot" 
 req_attr["physical.basedir"] == "/my-docroot"    -- same as doc-root unless changed, e.g. by mod_alias 

 -- All of them are readable, but not all of them are writable (or have no effect if you write to them).    Some examples writing changes: 

 -- change physical-path 
 req_attr["physical.path"] = ... 

 -- change query-string 
 req_attr["uri.query"] = ... 

 -- simple rewrite 
 req_attr["request.uri"] = ... 
 return lighty.RESTART_REQUEST 
 </pre> 

 Some @lighty.r.req_attr[]@ attributes provide similar values to those in the standard CGI/1.1 environment 
 |_. @lighty.r.req_attr[]@         |_. CGI/1.1 env var | 
 | @["uri.scheme"]@                | REQUEST_SCHEME      | 
 | @["uri.authority"]@             | SERVER_NAME         | 
 | @["uri.path"]@                  | SCRIPT_NAME         | 
 | @["uri.query"]@                 | QUERY_STRING        | 
 | @["request.method"]@            | REQUEST_METHOD      | 
 | @["request.protocol"]@          | SERVER_PROTOCOL     | 
 | @["request.path-info"]@         | PATH_INFO           | 
 | @["request.remote-addr"]@       | REMOTE_ADDR         | 
 | @["request.remote-port"]@       | REMOTE_PORT         | 
 | @["request.server-addr"]@       | SERVER_ADDR         | 
 | @["request.server-port"]@       | SERVER_PORT         | 
 | @["request.server-name"]@       | SERVER_NAME         | 
 | @["physical.doc-root"]@         | DOCUMENT_ROOT       | 
 | @["physical.basedir"]@          | DOCUMENT_ROOT       | 
 | @["physical.path"]@             | SCRIPT_FILENAME     | 
 | @["physical.rel-path"]@         | SCRIPT_NAME         | 


 h5. @lighty.r.req_attr[]@ writable attributes 

 Modifications to specific attributes or components are fairly direct interfaces 
 into lighttpd internals and do not affect other related attributes, including 
 the full request from which the attributes or component may have been derived. 

 What does this mean? 
 It means: *carefully test your scripts and verify desired behavior.* 

 If full request reprocessing is needed after any modification, 
 e.g. if modifications would affect config processing, script should 
 return @lighty.RESTART_REQUEST@ 

 |_. lighty.r.req_attr[]         |_. description | 
 | @["uri.scheme"]@              | modification has similar effect as changing scheme in mod_extforward 
                                 If reprocessing request is needed, then @return lighty.RESTART_REQUEST@ | 
 | @["uri.authority"]@           | modification should generally be repeated to @lighty.r.req_header["Host"]@ 
                                 If reprocessing request is needed, then @return lighty.RESTART_REQUEST@ | 
 | @["uri.path"]@                | modification discouraged; 
                                 derived from @["request.uri"]@, url-decoded and path-simplified; 
                                 prefer to modify @["request.uri"]@ and @return lighty.RESTART_REQUEST@ | 
 | @["uri.query"]@               | modification affects subsequent use of query-string by other modules 
                                 If reprocessing request is needed, prefer to modify @["request.uri"]@ and @return lighty.RESTART_REQUEST@ | 
 | @["request.uri"]@             | modification has similar effect as using mod_rewrite and should be followed by @return lighty.RESTART_REQUEST@  
                                 so that lighttpd reprocesses the request and reparses the URI into components. 
                                 @"request.uri"@ can be reconstructed using components: @["request.uri"]@ = @["uri.path-raw"]@ "?" @["uri.query"]@ | 
 | @["request.orig-uri"]@        | modification discouraged | 
 | @["request.path-info"]@       | modification affects subsequent use of path-info by other modules 
                                 If reprocessing request is needed, prefer to modify @["request.uri"]@ and @return lighty.RESTART_REQUEST@ | 
 | @["request.remote-addr"]@     | modification changes remote_addr _for all subsequent requests on connection_ (!!!) 
                                 modification has similar effect as using mod_extforward 
                                 (though mod_extforward additionally updates forwarding headers) 
                                 If reprocessing request is needed, then @return lighty.RESTART_REQUEST@ 
                                 (Prefer mod_extforward which manages remote addr per request in lighttpd 1.4.70+) | 
 | @["request.remote-port"]@     | modification changes remote_addr port for all subsequent requests on connection | 
 | @["physical.doc-root"]@       | modification affects subsequent use of the doc_root by other modules 
                                 (e.g. mod_ssi mod_webdav (limited use as fallback)) | 
 | @["physical.basedir"]@        | modification affects subsequent use of the basedir by other modules 
                                 (e.g. as DOCUMENT_ROOT passed to backend scripts, unless modified elsewhere) | 
 | @["physical.path"]@           | modification affects subsequent use of the path by other modules 
                                 (e.g. mod_staticfile and many other filesystem based modules) 
                                 modification has similar effect as using mod_alias 
                                 (when script called from @magnet.attract-physical-path-to@ hook) | 
 | @["physical.rel-path"]@       | modification affects subsequent use of the relative path by other modules 
                                 (e.g. mod_ssi mod_userdir mod_webdav) | 

 @["physical.*"]@ attributes are valid for scripts called from @magnet.attract-physical-path-to@ hook.    The attributes are not defined in earlier magnet hooks, and have almost no effect in later magnet hooks. 


 h5. @lighty.r.req_item[]@ readable attributes (since 1.4.65) 

 * req_item provides raw read-only access to select lighttpd internal request members 
 * req_item values are generally integers; req_attr values are generally strings 
 * req_item is not iterable; req_attr is iterable with pairs() 
 * Depending the magnet hook in which the lua script is run, 
   req_item values may not be set or may not be the final values. 

 |_. lighty.r.req_item[]           | (alternative)                  |_. description                               | 
 | @["http_status"]@               | @req_item.http_status@         | request HTTP status (0 if not yet set)      | 
 | @["req_header_len"]@            | @req_item.req_header_len@      | request header length                       | 
 | @["resp_header_len"]@           | @req_item.resp_header_len@     | response header length (0 if not yet set) | 
 | @["bytes_in"]@                  | @req_item.bytes_in@            | request bytes read                          | 
 | @["bytes_out"]@                 | @req_item.bytes_out@           | request bytes sent                          | 
 | @["stream_id"]@                 | @req_item.stream_id@           | HTTP/2 stream id                            | 
 | @["start_time"]()@              | @req_item.start_time()@        | request start time (secs, nsecs) 
                                                                  (note: function requires parentheses @()@)| 
 | @["req_count"]@                 | @req_item.req_count@           | requests started on connection 
                                                                  (note: HTTP/2 streams can be started in parallel)        | 
 | @["keep_alive"]@                | @req_item.keep_alive@          | request keep-alive state 
                                                                  (1 keep-alive; 0 close (or HTTP/2); -1 close, goaway) | 


 h5. @lighty.r.req_item[]@ writable attributes (since 1.4.65) 

 |_. lighty.r.req_item[]           | (alternative)                  |_. description                               | 
 | @["keep_alive"]@                | @req_item.keep_alive@          | request keep-alive state (settable to 0 or -1) 
                                                                  (1 keep-alive; 0 close (or HTTP/2); -1 close, goaway) | 

 . 


 h3. @lighty.server@ object 

 (since lighttpd 1.4.65) 

 |_. @lighty.r@                        |_. description | 
 | @lighty.server.irequests()@         | iterate over client requests | 
 | @lighty.server.plugin_stats[]@      | statistics for backend modules (equivalent to old API @lighty.status@) | 
 | @lighty.server.stats[]@             |server statistics | 

 h5. @lighty.server.irequests()@ (since 1.4.65) 

 * iterate over client requests 
 * Note: iterator object is *reused* each iteration 
   (copy out desired leaf info before iterating; save only strings or numbers) 
   (iterator object is not valid outside iteration loop; do not save iterator object) 
 * examples: [[ModMagnetExamples#lua-mod_status|lua mod_status]], [[ModMagnetExamples#lua-mod_evasive|lua mod_evasive]], [[ModMagnetExamples#lua-mod_uploadprogress|lua mod_uploadprogress]] 

 h5. @lighty.server.plugin_stats[]@ (since 1.4.65) 

 * statistics for backend modules 
   (equivalent to @lighty.status@) 
   (similar to output from [[Docs_ModStatus|mod_status]] @status.statistics-url@) 
 * example: mod_magnet can add statistics to the mod_status global statistics page. 
 ** lighttpd.conf 
   @server.modules += ("mod_magnet", "mod_status")@ 
   @status.statistics-url = "/server-counters"@ 
   @magnet.attract-raw-url-to = (server.docroot + "/counter.lua")@ 
 ** counter.lua 
   @local plugin_stats = lighty.server.plugin_stats@ 
   @plugin_stats["magnet.connections"] = plugin_stats["magnet.connections"] + 1@ 
 ** Result of accessing @/server-counters@ 
   @magnet.connections: 7@ 
   @fastcgi.backend.php-foo.0...@ 
   @fastcgi.backend.php-foo.1...@ 
   @fastcgi.backend.php-foo.load: 0@ 


 h5. @lighty.server.stats[]@ (since 1.4.65) 

 |_. lighty.server.stats[]           |_. description | 
 | @clients_open@                    | number of open client connections | 
 | @uptime@                          | uptime (in seconds) | 
 | @version@                         | version string | 

 . 


 h3. @lighty.c.*@ library functions 

 (since lighttpd 1.4.60) 

 <pre> 

 -- digests and passwords 

 lighty.c.time()               -- seconds since 1 Jan 1970 00:00:00 (cached; faster than os.time()) 
 lighty.c.hrtime()             -- (seconds, nanoseconds) since 1 Jan 1970 00:00:00 (high resolution) (since 1.4.65) 
 lighty.c.rand()               -- generate pseudo-random number 
 lighty.c.md()                 -- calculate message digest (md5,sha1,sha256,sha512) 
 lighty.c.hmac()               -- calculate HMAC             (md5,sha1,sha256,sha512) 
 lighty.c.digest_eq()          -- timing-safe comparison of two hex digests 
 lighty.c.secret_eq()          -- timing-safe comparison of two strings 

 -- decode/encode 

 lighty.c.b64urldec()          -- base64url decode (validate and decode) 
 lighty.c.b64urlenc()          -- base64url encode, no padding 
 lighty.c.b64dec()             -- base64 decode (validate and decode) 
 lighty.c.b64enc()             -- base64 encode, no padding 
 lighty.c.hexdec()             -- hex decode (validate and decode) 
 lighty.c.hexenc()             -- hex encode uc; lc w/ lua s = s:lower() 
 lighty.c.xmlenc()             -- xml-encode/html-encode: <>&'\"` 
 lighty.c.urldec()             -- url-decode 
 lighty.c.urlenc()             -- url-encode 
 lighty.c.urldec_query()       -- url-decode query-string into table (since 1.4.65) 
 lighty.c.urlenc_query()       -- url-encode query-string from table (since 1.4.65) 
 lighty.c.urlenc_normalize() -- url-encode normalization 
 lighty.c.fspath_simplify()    -- simplify fspath (remove "/." "/.." "/../" "//") 
 lighty.c.quoteddec()          -- decode MIME quoted-string (since 1.4.65) 
 lighty.c.quotedenc()          -- encode input as MIME quoted-string (since 1.4.65) 
 lighty.c.bsdec()              -- decode backspace-escaped string (since 1.4.65) 
 lighty.c.bsenc()              -- encode CTLs and non-ASCII input as hex (\xFF) backspace-escaped string (since 1.4.65) 
 lighty.c.bsenc_json()         -- encode CTLs and non-ASCII input as json (\uFFFF) backspace-escaped string (since 1.4.66) 

 -- misc 

 lighty.c.cookie_tokens()      -- parse HTTP Cookie header into table (since 1.4.65) 
 lighty.c.header_tokens()      -- parse HTTP header into sequence table (since 1.4.65) 
 lighty.c.readdir()            -- dir walk 
 lighty.c.readlink()           -- contents of symbolic link (since 1.4.72) 
 lighty.c.stat()               -- stat() path 
 </pre> 


 h5. message digest (md) and hash-based message authentication code (HMAC) 
 (MD5, SHA1, SHA256, SHA512) 

 @lighty.c.md("algo", "data")@ 
 @lighty.c.hmac("algo", "secret", "data")@ 
 * "algo" can be one of: "md5", "sha1", "sha256", "sha512" 
   (as long as lighttpd is compiled w/ crypto lib supporting those algorithms) 
 * returns uppercase hex string of digest 

 @lighty.c.digest_eq("digest1", "digest2")@ 
 * performs a timing-safe, case-insensitive comparison of two hex digests 
   (timing-safe comparison is slightly more secure than <code>digest1 == digest2</code>) 
 * "digest1" and "digest2" are hex strings (of binary digests) 
 * returns boolean true or false 

 @lighty.c.secret_eq("data1", "data2")@ 
 * performs a timing-safe comparison of two strings 
   (and attempts to hide differences in string lengths) 
   (timing-safe comparison is slightly more secure than <code>data1 == data2</code>) 
 * "data1" and "data2" are strings 
 * returns boolean true or false 

 h5. decode/encode 

 @lighty.c.b64urldec("base64url-string")@ 
 @lighty.c.b64urlenc("string")@ 
 * base64url decode/encode (without padding) 
 * RFC4648 base64url (URL- and filename-safe standard), not base64 (standard) 
   https://en.wikipedia.org/wiki/Base64 

 @lighty.c.b64dec("base64-string")@ 
 @lighty.c.b64enc("string")@ 
 * base64 decode/encode (without padding) 
 * RFC4648 base64 
   https://en.wikipedia.org/wiki/Base64 

 @lighty.c.urldec_query("query-string")@ (since 1.4.65) 
 @lighty.c.urlenc_query("query-string")@ (since 1.4.65) 
 * url-decode query-string into table or url-encode table into query-string 
 * table value for a decoded key is blank ("") if no '=' follows "key" 
 * table value for a decoded key is blank ("") if blank value follows "key=" 
 * encoding results in "key=" if value is blank ("") 

 @lighty.c.quoteddec("quoted-string")@ (since 1.4.65) 
 @lighty.c.quotedenc("string")@ (since 1.4.65) 
 * quoteddec: decode input quoted-string  
   (remove surrounding double-quotes, unescape quoted-pairs (@string.gsub(str, '\\(["\\])?', '%1')@)) 
   (simplistic convenience func; see @lighty.c.bsdec@ for more thorough backslash-escape decoding) 
 * quotedenc: encode input as quoted-string 
   (backslash-escape @"@ and @\@ to quoted-pairs (@string.gsub(str, '(["\\])', '\\%1')@), then surround with double-quotes) 
   (simplistic convenience func; see @lighty.c.bsenc@ for more thorough backslash-escape encoding) 

 @lighty.c.bsdec("escaped-string")@ (since 1.4.65) 
 * decode backspace-escaped string 
   (decodes @\"@, @\\@, @\n@ (and other well-known CTL \-escapes), octal @\000@, hex @\xFF@, json @\uFFFF@) (json since 1.4.66) 
   (removes double-quotes surrounding input, if present) 

 @lighty.c.bsenc("string")@ (since 1.4.65) 
 * encode control chars or non-ASCII input as hex (@\xFF@) backspace-escaped string (or well-known CTL \-escapes, e.g. @\n@) 
 * double-quote (@"@) and backslash (@\@) in input are also backslash-escaped 
 * result does not contain surrounding double-quotes; caller should add as appropriate 

 @lighty.c.bsenc_json("string")@ (since 1.4.66) 
 * encode control chars or non-ASCII input as json (@\uFFFF@) backspace-escaped string (or well-known CTL \-escapes, e.g. @\n@) 
 * double-quote (@"@) and backslash (@\@) in input are also backslash-escaped 
 * result does not contain surrounding double-quotes; caller should add as appropriate 

 h5. misc 

 @lighty.c.cookie_tokens("cookie-string")@ (since 1.4.65) 
 * parse HTTP Cookie header into table (Note: quoted-strings preserved as-is; not decoded) 
 * @local cookies = lighty.c.cookie_tokens(lighty.r.req_header['Cookie'])@ 
   @local identity = lighty.c.urldec(cookies["ident"])@ 

 @lighty.c.header_tokens("header-string")@ (since 1.4.65) 
 * parse HTTP header into sequence table (Note: quoted-strings preserved as-is; not decoded) 
 * sequence table contains works/tokens and separators (@,@ @;@ @=@) as separate elements 
   (optional whitespace (OWS) and bad whitespace (BWS) is removed) 
 * @local tokens = lighty.c.header_tokens(lighty.r.req_header['Accept-Encoding'])@ 
 * @for i = 1, #tokens do tok = lighty.c.quoteddec(tokens[i]) ......... end@ 
   In the case of @Accept-Encoding@, each encoding might optionally include @;q=0.x@ quality, 
   which would be multiple elements in the @tokens@ table (@;@ @q@ @=@ @0.x@) 

 @lighty.c.readdir("/path/to/dir")@ 
 * dir walk 
 * skips "." or ".." for convenience 
 * @local name@ 
   @for name in lighty.c.readdir("/tmp") do r.resp_body:add({name, "\n"}) end@ 

 @lighty.c.readlink("/path/to/softlink")@ 
 * returns the content of the softlink from @readlink@ call, not cached 

 @lighty.c.stat("/path")@ 
 * checks the existence of a file/dir/socket and returns the @stat()@ information for it 
 * uses lighttpd internal stat-cache 
 * path: (string) absolute path 
 * returns: table with the following fields, or nil on error 
 ** is_file 
 ** is_dir 
 ** is_char 
 ** is_block 
 ** is_socket 
 ** is_link 
 ** is_fifo 
 ** st_mode 
 ** st_mtime 
 ** st_ctime 
 ** st_atime 
 ** st_uid 
 ** st_gid 
 ** st_size 
 ** st_ino 
 ** etag 
 ** content-type 
 ** http-response-send-file    (similar to mod_staticfile) (since 1.4.64) 
 ** st_mtim() (returns secs, nsecs) (since 1.4.65) 
 ** st_ctim() (returns secs, nsecs) (since 1.4.65) 
 ** st_atim() (returns secs, nsecs) (since 1.4.65) 



 h2. lighttpd.conf configuration condition equivalents 

 [[Docs_Configuration#Conditional-Configuration|lighttpd.conf configuration condition]] mod_magnet lua equivalents 

 |_. [[Docs_Configuration#Conditional-Configuration|lighttpd.conf configuration condition]]|_. mod_magnet lua equivalent| 
 |@$HTTP["request-method"]@|@lighty.r.req_attr["request.method"]@| 
 |@$HTTP["scheme"]@|@lighty.r.req_attr["uri.scheme"]@| 
 |@$HTTP["host"]@|@lighty.r.req_attr["uri.authority"]@| 
 |@$HTTP["url"]@|@lighty.r.req_attr["uri.path"]@ 
                 @lighty.r.req_attr["uri.path-raw"]@| 
 |@$HTTP["querystring"]@|@lighty.r.req_attr["uri.query"]@| 
 |@$HTTP["remoteip"]@|@lighty.r.req_attr["request.remote-addr"]@ 
                      @lighty.r.req_attr["request.remote-port"]@| 
 |@$SERVER["socket"]@|@lighty.r.req_attr["request.server-addr"]@ 
                      @lighty.r.req_attr["request.server-port"]@| 
 |@$REQUEST_HEADER["..."]@|@lighty.r.req_header["..."]@| 
 |@$HTTP["cookie"]@|@lighty.r.req_header["Cookie"]@| 
 |@$HTTP["useragent"]@|@lighty.r.req_header["User-Agent"]@| 
 |@$HTTP["language"]@|@lighty.r.req_header["Accept-Language"]@| 
 |@$HTTP["referer"]@|@lighty.r.req_header["Referer"]@| 

 In mod_magnet stage @magnet.attract-physical-path-to@: 
 |physical path|@lighty.r.req_attr["physical.path"]@| 
 |physical path exists|@lighty.c.stat(...)@| 



 &nbsp; 

 h1. Deprecated 

 &nbsp; 

 h2. mod_magnet API before lighttpd 1.4.60 

 (Note: This old API is deprecated.    Please prefer using the [[Docs_ModMagnet#lightyr-request-object|lighty.r request object]]) 

 h3. @lighty.*@ tables 

 Most of the interaction between mod_magnet and lighttpd is done through tables. Tables in lua are similar to hashes (Perl, Ruby), dictionaries (Java, Python), associative arrays (PHP), ...    The following tables support @pairs()@ to iterate over their entries. 
 * lighty.request[] - certain request headers like Host, Cookie, or User-Agent are available (prefer: @lighty.r.req_header[]@) 
 * lighty.req_env[] - request environment variables (prefer: @lighty.r.req_env[]@) 
 * lighty.env[] - request attributes (prefer: @lighty.r.req_attr[]@) (see [[Docs_ModMagnet#lightyr-request-object|lighty.r request object]]) 
 * lighty.header[] - certain response headers like Location are available (prefer: @lighty.r.resp_header[]@) 
 * lighty.content[] - response body (prefer: @lighty.r.resp_body.set()@) 
 * lighty.status[] - statistics (prefer: @lighty.server.plugin_stats[]@) 


 h5. @lighty.header[]@ (prefer: @lighty.r.resp_header[]@) 

 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"@ 


 h5. @lighty.content[]@ (prefer: @lighty.r.resp_body.set()@) 

 You can generate your own content and send it as the response.    The @lighty.content[]@ table may contain strings, or tables of file info.    After the script finishes, the response is generated as the result of concatenating all table elements in order. 

 * Strings - copied into response 
 * Tables - file info representing response 
 ** filename = "<absolute-path>" is required 
 ** offset = <number> [default: 0] 
 ** length = <number> [default: size of the file] 
    This results in sending the range [offset, length-1] of the file. 
    ('length' param is misnamed and actually indicates the range offset + 1; kept as such for historical compatibility with existing scripts) 

 <pre>lighty.content = { "<pre>", { filename = "/etc/motd" }, "</pre>" } 
 lighty.header["Content-Type"] = "text/html" 
 return 200</pre> 



 h2. Porting mod_cml scripts 

 mod_cml got replaced by mod_magnet. 
 * mod_cml functions @memcache_get_string()@ @memcache_get_long()@ @memcache_exists()@ (and lighttpd.conf @cml.memcache-hosts@) should be replaced with a lua-only solution: 
   https://github.com/silentbicycle/lua-memcached 

 * mod_cml function @dir_files@ should be replaced with @lighty.c.readdir()@ (since 1.4.60) 

 * CACHE_HIT in mod_cml: 
 @output_include = { "file1", "file2" }@ 
 @return CACHE_HIT@ 
 becomes in mod_magnet: 
 @lighty.r.resp_body.set({ { filename = "/path/to/file1" }, { filename = "/path/to/file2"} })@ 
 @return 200@ 

 * CACHE_MISS in mod_cml: 
 @trigger_handler = "/index.php"@ 
 @return CACHE_MISS@ 
 becomes in mod_magnet: 
 @lighty.r.req_attr["request.uri"] = "/index.php"@ 
 @return lighty.RESTART_REQUEST@ 

 Questions?    Post in the "lighttpd Forums":https://redmine.lighttpd.net/projects/lighttpd/boards