mod_lua
描述: | 将 Lua 挂钩提供到 httpd 请求处理的各个部分 |
状态: | 延期 |
模块标识符: | lua_module |
源文件: | mod_lua.c |
兼容性: | 2.3 及以后 |
摘要
该模块允许使用 Lua 编程语言编写的脚本扩展服务器。mod_lua可用的扩展点(挂钩)包括本机编译的 Apache HTTP Server 模块可用的许多挂钩,例如将请求映射到 files,生成动态响应,访问控制,身份验证和授权
有关 Lua 编程语言的更多信息可以在Lua 网站找到。
警告
该模块比 httpd 拥有强大的功能,这既是强项又是潜在的安全风险。不建议您在与您不信任的用户共享的服务器上使用此模块,因为它可能会被滥用来更改 httpd 的内部工作方式。
基本 Configuration
基本模块 loading 指令是
LoadModule lua_module modules/mod_lua.so
mod_lua
提供了一个名为lua-script
的处理程序,它可以与SetHandler或AddHandler指令一起使用:
<Files "*.lua"> SetHandler lua-script </Files>
这将导致mod_lua
通过调用该文件的handle
function 来处理以.lua
结尾的 files 请求。
有关更多灵活性,请参阅LuaMapHandler
。
编写处理程序
在 Apache HTTP Server API 中,处理程序是一种特定类型的 hook,负责生成响应。包含处理程序的模块示例包括mod_proxy,mod_cgi和mod_status。
mod_lua
总是看起来为处理程序调用 Lua function,而不仅仅是评估脚本体 CGI 样式。处理程序 function 看起来像这样:
example.lua -- example handler require "string" --[[ This is the default method name for Lua handlers, see the optional function-name in the LuaMapHandler directive to choose a different entry point. --]] function handle(r) r.content_type = "text/plain" if r.method == 'GET' then r:puts("Hello Lua World!n") for k, v in pairs( r:parseargs() ) do r:puts( string.format("%s: %sn", k, v) ) end elseif r.method == 'POST' then r:puts("Hello Lua World!n") for k, v in pairs( r:parsebody() ) do r:puts( string.format("%s: %sn", k, v) ) end elseif r.method == 'PUT' then -- use our own Error contents r:puts("Unsupported HTTP method " .. r.method) r.status = 405 return apache2.OK else -- use the ErrorDocument return 501 end return apache2.OK end
此处理程序 function 只打印出 uri 或表单编码的 arguments 到明文页面。
这意味着(实际上鼓励)您可以在同一个脚本中拥有多个处理程序(或挂钩或过滤器)。
编写授权提供者
mod_authz_core为授权提供 high-level 接口,比直接使用相关挂钩更容易使用。要求指令的第一个参数给出了负责授权提供程序的 name。对于任何要求 line,mod_authz_core将调用给定 name 的授权提供程序,将 line 的 rest 作为参数传递。然后,提供程序将检查授权并将结果作为 return value 传递。
authz 提供程序通常在身份验证之前调用。如果需要知道经过身份验证的用户 name(或者用户将完全通过身份验证),则提供程序必须 return apache2.AUTHZ_DENIED_NO_USER
。这将导致身份验证继续进行,并且 authz 提供程序将被调用为第二个 time。
以下 authz 提供程序 function 需要两个 arguments,一个 ip 地址和一个用户 name。它将允许从给定的 IP 地址进行访问而无需身份验证,或者如果经过身份验证的用户匹配第二个参数:
authz_provider.lua require 'apache2' function authz_check_foo(r, ip, user) if r.useragent_ip == ip then return apache2.AUTHZ_GRANTED elseif r.user == nil then return apache2.AUTHZ_DENIED_NO_USER elseif r.user == user then return apache2.AUTHZ_GRANTED else return apache2.AUTHZ_DENIED end end
以下 configuration 将此 function 注册为 provider foo
并将其配置为 URL /
:
LuaAuthzProvider foo authz_provider.lua authz_check_foo <Location "/"> Require foo 10.1.2.3 john_doe </Location>
书写钩子
Hook 函数是模块(和 Lua 脚本)参与处理请求的方式。服务器公开的每种类型的 hook 都是出于特定目的,例如将请求映射到文件系统,执行访问控制或设置 mime 类型:
Hook 阶段 | mod_lua 指令 | 描述 |
快速处理程序 | LuaQuickHandler | 这是在将请求映射到 host 或虚拟 host 之后将调用的第一个 hook |
翻译 name | LuaHookTranslateName | 此阶段将请求的 URI 转换为系统上的文件名。诸如mod_alias和mod_rewrite的模块在此阶段运行。 |
Map 到存储 | LuaHookMapToStorage | 此阶段 maps files 到其物理,缓存或 external/proxied 存储。它可以由代理或缓存模块使用 |
检查访问权限 | LuaHookAccessChecker | 此阶段检查 client 是否可以访问资源。在用户通过身份验证之前,此阶段是 run,因此请注意。 |
检查用户 ID | LuaHookCheckUserID | 此阶段用于检查协商的用户 ID |
检查授权 | LuaHookAuthChecker或LuaAuthzProvider | 此阶段根据协商的凭据授权用户,例如用户 ID,客户端证书等。 |
检查类型 | LuaHookTypeChecker | 此阶段检查所请求的文件并为其分配 content type 和处理程序 |
修复程序 | LuaHookFixups | 这是内容处理程序运行之前的最终“修复任何问题”阶段。应在此处对请求进行任何 last-minute 更改。 |
内容处理程序 | FX。.lua files 或通过LuaMapHandler | 这是处理内容的地方。 Files 被读取,解析,一些是 run,结果被发送到 client |
Logging | LuaHookLog | 处理完请求后,它会进入多个 logging 阶段,这些阶段会在错误或访问 log 中记录请求。 Mod_lua 能够 hook 到此开头并控制 logging 输出。 |
Hook 函数作为唯一参数传递请求 object(LuaAuthzProvider 除外,它也从 Require 指令传递 arguments)。它们可以 return 任何 value,具体取决于 hook,但最常见的是它们 return OK,DONE 或 DECLINED,你可以在 Lua 中写为apache2.OK
,apache2.DONE
或apache2.DECLINED
,或者是 HTTP 状态 code。
translate_name.lua -- example hook that rewrites the URI to a filesystem path. require 'apache2' function translate_name(r) if r.uri == "/translate-name" then r.filename = r.document_root .. "/find_me.txt" return apache2.OK end -- we don't care about this URL, give another module a chance return apache2.DECLINED end
translate_name2.lua --[[ example hook that rewrites one URI to another URI. It returns a apache2.DECLINED to give other URL mappers a chance to work on the substitution, including the core translate_name hook which maps based on the DocumentRoot. Note: Use the early/late flags in the directive to make it run before or after mod_alias. --]] require 'apache2' function translate_name(r) if r.uri == "/translate-name" then r.uri = "/find_me.txt" return apache2.DECLINED end return apache2.DECLINED end
数据结构
- request_rec
request_rec 作为用户数据映射。它有一个 metatable,可以让你用它做有用的事情。在大多数情况下,它具有与 request_rec 结构相同的字段,其中许多是可写的和可读的。(table 字段的内容可以更改,但字段本身不能设置为不同 tables.)Name Lua 类型可写描述 allowoverrides string no 应用于当前请求的 AllowOverride 选项.ap_auth_type string no 如果进行了身份验证检查,则设置为验证类型(f.x.basic)args string yes 查询 string arguments 从请求中提取(f.x.foo=bar&name=johnsmith)assbackwards boolean no 设置为 true 如果这是 HTTP/0.9 样式请求(e.g. GET /foo(没有 headers)))authname string no 用于授权的领域 name(如果适用).flash string 否服务器横幅,f.x。Apache HTTP Server/2.4.3 openssl/0.9.8c basic_auth_pw string no 使用此请求发送的基本验证密码,如果有的话 canonical_filename string 否规范文件名请求 content_encoding string 否当前请求的内容编码 content_type string yes 当前请求的 content type,在 type_check 阶段确定(f.x.image/gif 或 text/html)context_prefix string no context_document_root string no document_root string no host errheaders_out table 的文档根没有用于响应的 MIME 头环境,甚至在出现错误时打印并在内部重定向中保留文件名 string yes 请求 maps 的文件 name,f.x。/www/example.com/foo.txt。这可以在请求的 translate-name 或 map-to-storage 阶段进行更改,以允许默认处理程序(或脚本处理程序)提供与请求文件不同的文件。 handler string yes 应该为此请求提供服务的处理程序的 name,f.x。 lua-script 如果由 mod_lua 提供。这通常由 AddHandler 或 SetHandler 指令设置,但也可以通过 mod_lua 设置,以允许另一个处理程序提供特定的请求,否则该请求将不会由它提供。 headers_in table yes 来自请求的 MIME 头环境。这包含_Haders,User-Agent,Referer 等 headers。 headers_out table yes 响应的 MIME 头环境。 hostname string no host name,由 Host:标头或完整 URI 设置。 is_https boolean 否此请求是否通过 HTTPS 完成 is_initial_req boolean no 此请求是初始请求还是 sub-request limit_req_body 数字否此请求的请求正文的大小限制,如果没有限制则为 0。 log_id string no 用于标识访问和错误 log 中的请求的 ID。 method string no 请求方法,f.x。 GET 或 POST。 notes table yes 可以从一个模块传递到另一个模块的注释列表。 options string no 应用于当前请求的 Options 指令。 path_info string no 从此请求中提取 PATH_INFO。 port number no 请求使用的服务器 port。 protocol string no 使用的协议,f.x。 HTTP/1.1 proxyreq string yes 表示这是否是代理请求。此 value 通常设置在请求的 post_read_request/translatename 阶段。 range string no Range:标头的内容。剩余数量 no 从请求正文中读取的剩余字节数。 server_built string no 构建服务器可执行文件的 time。 servername string 否此请求的服务器 name。 some_auth_required boolean 否此请求是否需要某些授权 is/was。 subprocess_env table yes 为此请求设置的环境变量。已启动编号否服务器的 time(re)started,自纪元(1970 年 1 月 1 日)以来的秒数)状态编号是此请求的(当前)HTTP return code,f.x。200 或 404. the_request string 否请求 string 已发送由 client,f.x。GET /foo/bar HTTP/1.1. unparsed_uri string no 请求的未解析 URI uri string yes httpd 用户解析后的 URI string yes 如果进行了身份验证检查,则将其设置为 name 经过身份验证的用户.useragent_ip string no 发出请求的用户代理的 IP
内置功能
request_rec object 具有(至少)以下方法:
r:flush() -- flushes the output buffer. -- Returns true if the flush was successful, false otherwise. while we_have_stuff_to_send do r:puts("Bla bla blan") -- print something to client r:flush() -- flush the buffer (send to client) r.usleep(500000) -- fake processing time for 0.5 sec. and repeat end
r:add_output_filter(filter_name) -- add an output filter: r:add_output_filter("fooFilter") -- add the fooFilter to the output stream
r:sendfile(filename) -- sends an entire file to the client, using sendfile if supported by the current platform: if use_sendfile_thing then r:sendfile("/var/www/large_file.img") end
r:parseargs() -- returns two tables; one standard key/value table for regular GET data, -- and one for multi-value data (fx. foo=1&foo=2&foo=3): local GET, GETMULTI = r:parseargs() r:puts("Your name is: " .. GET['name'] or "Unknown")
r:parsebody([sizeLimit]) -- parse the request body as a POST and return two lua tables, -- just like r:parseargs(). -- An optional number may be passed to specify the maximum number -- of bytes to parse. Default is 8192 bytes: local POST, POSTMULTI = r:parsebody(1024*1024) r:puts("Your name is: " .. POST['name'] or "Unknown")
r:puts("hello", " world", "!") -- print to response body, self explanatory
r:write("a single string") -- print to response body, self explanatory
r:escape_html("<html>test</html>") -- Escapes HTML code and returns the escaped result
r:base64_encode(string) -- Encodes a string using the Base64 encoding standard: local encoded = r:base64_encode("This is a test") -- returns VGhpcyBpcyBhIHRlc3Q=
r:base64_decode(string) -- Decodes a Base64-encoded string: local decoded = r:base64_decode("VGhpcyBpcyBhIHRlc3Q=") -- returns 'This is a test'
r:md5(string) -- Calculates and returns the MD5 digest of a string (binary safe): local hash = r:md5("This is a test") -- returns ce114e4501d2f4e2dcea3e17b546f339
r:sha1(string) -- Calculates and returns the SHA1 digest of a string (binary safe): local hash = r:sha1("This is a test") -- returns a54d88e06612d820bc3be72877c74f257b561b19
r:escape(string) -- URL-Escapes a string: local url = "http://foo.bar/1 2 3 & 4 + 5" local escaped = r:escape(url) -- returns 'http%3a%2f%2ffoo.bar%2f1+2+3+%26+4+%2b+5'
r:unescape(string) -- Unescapes an URL-escaped string: local url = "http%3a%2f%2ffoo.bar%2f1+2+3+%26+4+%2b+5" local unescaped = r:unescape(url) -- returns 'http://foo.bar/1 2 3 & 4 + 5'
r:construct_url(string) -- Constructs an URL from an URI local url = r:construct_url(r.uri)
r.mpm_query(number) -- Queries the server for MPM information using ap_mpm_query: local mpm = r.mpm_query(14) if mpm == 1 then r:puts("This server uses the Event MPM") end
r:expr(string) -- Evaluates an expr string. if r:expr("%{HTTP_HOST} =~ /^www/") then r:puts("This host name starts with www") end
r:scoreboard_process(a) -- Queries the server for information about the process at position a: local process = r:scoreboard_process(1) r:puts("Server 1 has PID " .. process.pid)
r:scoreboard_worker(a, b) -- Queries for information about the worker thread, b, in process a: local thread = r:scoreboard_worker(1, 1) r:puts("Server 1's thread 1 has thread ID " .. thread.tid .. " and is in " .. thread.status .. " status")
r:clock() -- Returns the current time with microsecond precision
r:requestbody(filename) -- Reads and returns the request body of a request. -- If 'filename' is specified, it instead saves the -- contents to that file: local input = r:requestbody() r:puts("You sent the following request body to me:n") r:puts(input)
r:add_input_filter(filter_name) -- Adds 'filter_name' as an input filter
r.module_info(module_name) -- Queries the server for information about a module local mod = r.module_info("mod_lua.c") if mod then for k, v in pairs(mod.commands) do r:puts( ("%s: %sn"):format(k,v)) -- print out all directives accepted by this module end end
r:loaded_modules() -- Returns a list of modules loaded by httpd: for k, module in pairs(r:loaded_modules()) do r:puts("I have loaded module " .. module .. "n") end
r:runtime_dir_relative(filename) -- Compute the name of a run-time file (e.g., shared memory "file") -- relative to the appropriate run-time directory.
r:server_info() -- Returns a table containing server information, such as -- the name of the httpd executable file, mpm used etc.
r:set_document_root(file_path) -- Sets the document root for the request to file_path
r:set_context_info(prefix, docroot) -- Sets the context prefix and context document root for a request
r:os_escape_path(file_path) -- Converts an OS path to a URL in an OS dependent way
r:escape_logitem(string) -- Escapes a string for logging
r.strcmp_match(string, pattern) -- Checks if 'string' matches 'pattern' using strcmp_match (globs). -- fx. whether 'www.example.com' matches '*.example.com': local match = r.strcmp_match("foobar.com", "foo*.com") if match then r:puts("foobar.com matches foo*.com") end
r:set_keepalive() -- Sets the keepalive status for a request. Returns true if possible, false otherwise.
r:make_etag() -- Constructs and returns the etag for the current request.
r:send_interim_response(clear) -- Sends an interim (1xx) response to the client. -- if 'clear' is true, available headers will be sent and cleared.
r:custom_response(status_code, string) -- Construct and set a custom response for a given status code. -- This works much like the ErrorDocument directive: r:custom_response(404, "Baleted!")
r.exists_config_define(string) -- Checks whether a configuration definition exists or not: if r.exists_config_define("FOO") then r:puts("httpd was probably run with -DFOO, or it was defined in the configuration") end
r:state_query(string) -- Queries the server for state information
r:stat(filename [,wanted]) -- Runs stat() on a file, and returns a table with file information: local info = r:stat("/var/www/foo.txt") if info then r:puts("This file exists and was last modified at: " .. info.modified) end
r:regex(string, pattern [,flags]) -- Runs a regular expression match on a string, returning captures if matched: local matches = r:regex("foo bar baz", [[foo (w+) (S*)]]) if matches then r:puts("The regex matched, and the last word captured ($2) was: " .. matches[2]) end -- Example ignoring case sensitivity: local matches = r:regex("FOO bar BAz", [[(foo) bar]], 1) -- Flags can be a bitwise combination of: -- 0x01: Ignore case -- 0x02: Multiline search
r.usleep(number_of_microseconds) -- Puts the script to sleep for a given number of microseconds.
r:dbacquire(dbType[, dbParams]) -- Acquires a connection to a database and returns a database class. -- See 'Database connectivity' for details.
r:ivm_set("key", value) -- Set an Inter-VM variable to hold a specific value. -- These values persist even though the VM is gone or not being used, -- and so should only be used if MaxConnectionsPerChild is > 0 -- Values can be numbers, strings and booleans, and are stored on a -- per process basis (so they won't do much good with a prefork mpm) r:ivm_get("key") -- Fetches a variable set by ivm_set. Returns the contents of the variable -- if it exists or nil if no such variable exists. -- An example getter/setter that saves a global variable outside the VM: function handle(r) -- First VM to call this will get no value, and will have to create it local foo = r:ivm_get("cached_data") if not foo then foo = do_some_calcs() -- fake some return value r:ivm_set("cached_data", foo) -- set it globally end r:puts("Cached data is: ", foo) end
r:htpassword(string [,algorithm [,cost]]) -- Creates a password hash from a string. -- algorithm: 0 = APMD5 (default), 1 = SHA, 2 = BCRYPT, 3 = CRYPT. -- cost: only valid with BCRYPT algorithm (default = 5).
r:mkdir(dir [,mode]) -- Creates a directory and sets mode to optional mode parameter.
r:mkrdir(dir [,mode]) -- Creates directories recursive and sets mode to optional mode parameter.
r:rmdir(dir) -- Removes a directory.
r:touch(file [,mtime]) -- Sets the file modification time to current time or to optional mtime msec value.
r:get_direntries(dir) -- Returns a table with all directory entries. function handle(r) local dir = r.context_document_root for _, f in ipairs(r:get_direntries(dir)) do local info = r:stat(dir .. "/" .. f) if info then local mtime = os.date(fmt, info.mtime / 1000000) local ftype = (info.filetype == 2) and "[dir] " or "[file]" r:puts( ("%s %s %10i %sn"):format(ftype, mtime, info.size, f) ) end end end
r.date_parse_rfc(string) -- Parses a date/time string and returns seconds since epoche.
r:getcookie(key) -- Gets a HTTP cookie
r:setcookie{ key = [key], value = [value], expires = [expiry], secure = [boolean], httponly = [boolean], path = [path], domain = [domain] } -- Sets a HTTP cookie, for instance: r:setcookie{ key = "cookie1", value = "HDHfa9eyffh396rt", expires = os.time() + 86400, secure = true }
r:wsupgrade() -- Upgrades a connection to WebSockets if possible (and requested): if r:wsupgrade() then -- if we can upgrade: r:wswrite("Welcome to websockets!") -- write something to the client r:wsclose() -- goodbye! end
r:wsread() -- Reads a WebSocket frame from a WebSocket upgraded connection (see above): local line, isFinal = r:wsread() -- isFinal denotes whether this is the final frame. -- If it isn't, then more frames can be read r:wswrite("You wrote: " .. line)
r:wswrite(line) -- Writes a frame to a WebSocket client: r:wswrite("Hello, world!")
r:wsclose() -- Closes a WebSocket request and terminates it for httpd: if r:wsupgrade() then r:wswrite("Write something: ") local line = r:wsread() or "nothing" r:wswrite("You wrote: " .. line); r:wswrite("Goodbye!") r:wsclose() end
Logging 功能
-- examples of logging messages r:trace1("This is a trace log message") -- trace1 through trace8 can be used r:debug("This is a debug log message") r:info("This is an info log message") r:notice("This is a notice log message") r:warn("This is a warn log message") r:err("This is an err log message") r:alert("This is an alert log message") r:crit("This is a crit log message") r:emerg("This is an emerg log message")
apache2 包
名为apache2
的包可用于(至少)以下内容。
- apache2.OK
内部常数 OK。如果处理者处理了请求,处理程序应该 return。 - apache2.DECLINED 内部常量下降。如果他们不打算处理请求,处理程序应该 return。
- apache2.DONE
内部常数 DONE。 - apache2.version
Apache HTTP 服务器 version string - apache2.HTTP_MOVED_TEMPORARILY
HTTP 状态 code - apache2.PROXYREQNONE,apache2.PROXYREQ_PROXY,apache2.PROXYREQ_REVERSE,apache2.PROXYREQ_RESPONSE
mod_proxy 使用的内部常量 - apache2.AUTHZ_DENIED,apache2.AUTHZ_GRANTED,apache2.AUTHZ_NEUTRAL,apache2.AUTHZ_GENERAL_ERROR,apache2.AUTHZ_DENIED_NO_USER
mod_authz_core 使用的内部常量
(其他 HTTP 状态代码尚未 implemented.)
使用 Lua 过滤器修改内容
通过LuaInputFilter或LuaOutputFilter实现的过滤器函数被设计为 three-stage non-blocking 函数,使用协同程序暂停和恢复函数,因为存储桶是在过滤器链下发送的。这种功能的核心结构是:
function filter(r) -- Our first yield is to signal that we are ready to receive buckets. -- Before this yield, we can set up our environment, check for conditions, -- and, if we deem it necessary, decline filtering a request alltogether: if something_bad then return -- This would skip this filter. end -- Regardless of whether we have data to prepend, a yield MUST be called here. -- Note that only output filters can prepend data. Input filters must use the -- final stage to append data to the content. coroutine.yield([optional header to be prepended to the content]) -- After we have yielded, buckets will be sent to us, one by one, and we can -- do whatever we want with them and then pass on the result. -- Buckets are stored in the global variable 'bucket', so we create a loop -- that checks if 'bucket' is not nil: while bucket ~= nil do local output = mangle(bucket) -- Do some stuff to the content coroutine.yield(output) -- Return our new content to the filter chain end -- Once the buckets are gone, 'bucket' is set to nil, which will exit the -- loop and land us here. Anything extra we want to append to the content -- can be done by doing a final yield here. Both input and output filters -- can append data to the content in this phase. coroutine.yield([optional footer to be appended to the content]) end
数据库连接
Mod_lua 实现了一个简单的数据库 feature,用于在最流行的数据库引擎(mySQL,PostgreSQL,FreeTDS,ODBC,SQLite,Oracle)以及 mod_dbd 上查询和运行命令。
下面的 example 显示了如何从 table 获取数据库句柄和 return 信息:
function handle(r) -- Acquire a database handle local database, err = r:dbacquire("mysql", "server=localhost,user=someuser,pass=somepass,dbname=mydb") if not err then -- Select some information from it local results, err = database:select(r, "SELECT `name`, `age` FROM `people` WHERE 1") if not err then local rows = results(0) -- fetch all rows synchronously for k, row in pairs(rows) do r:puts( string.format("Name: %s, Age: %s", row[1], row[2]) ) end else r:puts("Database query error: " .. err) end database:close() else r:puts("Could not connect to the database: " .. err) end end
要使用mod_dbd,请将mod_dbd
指定为数据库类型,或将该字段留空:
local database = r:dbacquire("mod_dbd")
Database object 和包含的函数
dbacquire
返回的数据库 object 具有以下方法:
正常选择和从数据库查询:
-- Run a statement and return the number of rows affected: local affected, errmsg = database:query(r, "DELETE FROM `tbl` WHERE 1") -- Run a statement and return a result set that can be used synchronously or async: local result, errmsg = database:select(r, "SELECT * FROM `people` WHERE 1")
使用准备好的陈述(推荐):
-- Create and run a prepared statement: local statement, errmsg = database:prepare(r, "DELETE FROM `tbl` WHERE `age` > %u") if not errmsg then local result, errmsg = statement:query(20) -- run the statement with age > 20 end -- Fetch a prepared statement from a DBDPrepareSQL directive: local statement, errmsg = database:prepared(r, "someTag") if not errmsg then local result, errmsg = statement:select("John Doe", 123) -- inject the values "John Doe" and 123 into the statement end
转义值,关闭数据库等:
-- Escape a value for use in a statement: local escaped = database:escape(r, [["'|blabla]]) -- Close a database connection and free up handles: database:close() -- Check whether a database connection is up and running: local connected = database:active()
使用结果 sets
db:select
或通过db:prepare
创建的预准备语句函数返回的结果集可用于同步或异步获取行,具体取决于指定的行号:result(0)
以同步方式获取所有行,返回 table 行。result(-1)
异步提取集合中的下一个可用行。result(N)
异步提取行号N
:
-- fetch a result set using a regular query: local result, err = db:select(r, "SELECT * FROM `tbl` WHERE 1") local rows = result(0) -- Fetch ALL rows synchronously local row = result(-1) -- Fetch the next available row, asynchronously local row = result(1234) -- Fetch row number 1234, asynchronously local row = result(-1, true) -- Fetch the next available row, using row names as key indexes.
可以构造一个 function,它返回一个迭代 function,以同步或异步方式迭代所有行,具体取决于 async 参数:
function rows(resultset, async) local a = 0 local function getnext() a = a + 1 local row = resultset(-1) return row and a or nil, row end if not async then return pairs(resultset(0)) else return getnext, self end end local statement, err = db:prepare(r, "SELECT * FROM `tbl` WHERE `age` > %u") if not err then -- fetch rows asynchronously: local result, err = statement:select(20) if not err then for index, row in rows(result, true) do .... end end -- fetch rows synchronously: local result, err = statement:select(20) if not err then for index, row in rows(result, false) do .... end end end
关闭数据库连接
数据库句柄应在不再需要时使用database:close()
关闭。如果不手动关闭它们,它们最终将被垃圾收集并由 mod_lua 关闭,但是如果将关闭最后保留为 mod_lua,则最终可能会有太多未使用的数据库连接。基本上,以下两个措施是相同的:
-- Method 1: Manually close a handle local database = r:dbacquire("mod_dbd") database:close() -- All done -- Method 2: Letting the garbage collector close it local database = r:dbacquire("mod_dbd") database = nil -- throw away the reference collectgarbage() -- close the handle via GC
使用数据库时的注意事项
尽管标准的query
和run
函数是免费提供的,但建议您尽可能使用准备好的 statements 来优化 performance(如果您的 db 句柄持续 longtime)并最小化 SQL 注入攻击的风险。只有在没有变量插入语句(静态语句)时才应使用run
和query
。使用动态语句时,请使用db:prepare
或db:prepared
。
LuaAuthzProvider 指令
描述: | 将授权提供程序 function 插入mod_authz_core |
句法: | LuaAuthzProvider provider_name /path/to/lua/script.lua function_name |
Context: | 服务器配置 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.4.3 及以后 |
在 lua function 注册为授权提供程序后,它可以与要求指令一起使用:
LuaRoot "/usr/local/apache2/lua" LuaAuthzProvider foo authz.lua authz_check_foo <Location "/"> Require foo johndoe </Location>
require "apache2" function authz_check_foo(r, who) if r.user ~= who then return apache2.AUTHZ_DENIED return apache2.AUTHZ_GRANTED end
LuaCodeCache 指令
描述: | 配置已编译的 code 缓存。 |
句法: | LuaCodeCache stat\|forever\|never |
默认: | LuaCodeCache stat |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
指定 in-memory code 缓存的行为。默认值为 stat,它将每个 time 时间的顶级 level 脚本(不是任何包含的脚本)统计为该文件,如果修改后的 time 表示它比已加载的文件更新,则重新加载它。其他值导致它永远保持文件缓存(不进行统计和替换)或永远不缓存文件。
一般来说,stat 或者永远都适合 production,stat 或者永远不用于开发。
例子:
LuaCodeCache stat LuaCodeCache forever LuaCodeCache never
LuaHookAccessChecker 指令
描述: | 为 access_checker 阶段的请求处理提供 hook |
句法: | LuaHookAccessChecker /path/to/lua/script.lua hook_function_name[early\|late] |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.3.15 及更高版本支持可选的第三个参数 |
将 hook 添加到 access_checker 阶段。访问检查器 hook function 通常返回 OK,DECLINED 或 HTTP_FORBIDDEN。
Ordering
当此脚本相对于其他模块运行时,可选的 arguments“early”或“late”控件。
LuaHookAuthChecker 指令
描述: | 为 auth_checker 阶段的请求处理提供 hook |
句法: | LuaHookAuthChecker /path/to/lua/script.lua hook_function_name[early\|late] |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.3.15 及更高版本支持可选的第三个参数 |
在处理请求的 auth_checker 阶段调用 lua function。这可用于实现任意身份验证和授权检查。一个非常简单的例子:
require 'apache2' -- fake authcheck hook -- If request has no auth info, set the response header and -- return a 401 to ask the browser for basic auth info. -- If request has auth info, don't actually look at it, just -- pretend we got userid 'foo' and validated it. -- Then check if the userid is 'foo' and accept the request. function authcheck_hook(r) -- look for auth info auth = r.headers_in['Authorization'] if auth ~= nil then -- fake the user r.user = 'foo' end if r.user == nil then r:debug("authcheck: user is nil, returning 401") r.err_headers_out['WWW-Authenticate'] = 'Basic realm="WallyWorld"' return 401 elseif r.user == "foo" then r:debug('user foo: OK') else r:debug("authcheck: user='" .. r.user .. "'") r.err_headers_out['WWW-Authenticate'] = 'Basic realm="WallyWorld"' return 401 end return apache2.OK end
Ordering
当此脚本相对于其他模块运行时,可选的 arguments“early”或“late”控件。
LuaHookCheckUserID 指令
描述: | 为 check_user_id 阶段的请求处理提供 hook |
句法: | LuaHookCheckUserID /path/to/lua/script.lua hook_function_name[early\|late] |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.3.15 及更高版本支持可选的第三个参数 |
Ordering
当此脚本相对于其他模块运行时,可选的 arguments“early”或“late”控件。
LuaHookFixups 指令
描述: | 为请求处理的 fixups 阶段提供 hook |
句法: | LuaHookFixups /path/to/lua/script.lua hook_function_name |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
就像 LuaHookTranslateName 一样,但是在 fixups 阶段执行
LuaHookInsertFilter 指令
描述: | 为 insert_filter 阶段的请求处理提供 hook |
句法: | LuaHookInsertFilter /path/to/lua/script.lua hook_function_name |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
尚未实现
LuaHookLog 指令
描述: | 为请求处理的访问 log 阶段提供 hook |
句法: | LuaHookLog /path/to/lua/script.lua log_function_name |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
这个简单的 logging hook 允许你在 httpd 进入请求的 logging 阶段时运行 function。有了它,您可以将数据附加到您自己的日志,在编写常规 log 之前操作数据,或者阻止创建 log 条目。为了防止通常的 logging 发生,只需 return apache2.DONE
在你的 logging 处理程序中,否则 return apache2.OK
告诉 httpd 到 log 正常。
例:
LuaHookLog "/path/to/script.lua" logger
-- /path/to/script.lua -- function logger(r) -- flip a coin: -- If 1, then we write to our own Lua log and tell httpd not to log -- in the main log. -- If 2, then we just sanitize the output a bit and tell httpd to -- log the sanitized bits. if math.random(1,2) == 1 then -- Log stuff ourselves and don't log in the regular log local f = io.open("/foo/secret.log", "a") if f then f:write("Something secret happened at " .. r.uri .. "n") f:close() end return apache2.DONE -- Tell httpd not to use the regular logging functions else r.uri = r.uri:gsub("somesecretstuff", "") -- sanitize the URI return apache2.OK -- tell httpd to log it. end end
LuaHookMapToStorage 指令
描述: | 为 map_to_storage 阶段的请求处理提供 hook |
句法: | LuaHookMapToStorage /path/to/lua/script.lua hook_function_name |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
与LuaHookTranslateName
类似,但在请求的 map-to-storage 阶段执行。像这个阶段的 mod_cache run 这样的模块,这使得一个有趣的示例在这里做什么:
LuaHookMapToStorage "/path/to/lua/script.lua" check_cache
require"apache2" cached_files = {} function read_file(filename) local input = io.open(filename, "r") if input then local data = input:read("*a") cached_files[filename] = data file = cached_files[filename] input:close() end return cached_files[filename] end function check_cache(r) if r.filename:match("%.png$") then -- Only match PNG files local file = cached_files[r.filename] -- Check cache entries if not file then file = read_file(r.filename) -- Read file into cache end if file then -- If file exists, write it out r.status = 200 r:write(file) r:info(("Sent %s to client from cache"):format(r.filename)) return apache2.DONE -- skip default handler for PNG files end end return apache2.DECLINED -- If we had nothing to do, let others serve this. end
LuaHookTranslateName 指令
描述: | 为请求处理的 translate name 阶段提供 hook |
句法: | LuaHookTranslateName /path/to/lua/script.lua hook_function_name[early\|late] |
Context: | server config,virtual host |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.3.15 及更高版本支持可选的第三个参数 |
将 hook(在 APRHOOK_MIDDLE)添加到请求处理的 translate name 阶段。 hook function 接收一个参数 request_rec,并且应该 return 一个状态 code,它是一个 HTTP 错误 code,或者是 apache2 模块中定义的常量:apache2.OK,apache2.DECLINED 或 apache2.DONE。
对于那些新的钩子,基本上每个 hook 都会被调用,直到其中一个返回 apache2.OK。如果你的 hook 不想进行翻译,那么它应该 return apache2.DECLINED。如果请求应该停止处理,则 return apache2.DONE。
例:
# httpd.conf LuaHookTranslateName "/scripts/conf/hooks.lua" silly_mapper
-- /scripts/conf/hooks.lua -- require "apache2" function silly_mapper(r) if r.uri == "/" then r.filename = "/var/www/home.lua" return apache2.OK else return apache2.DECLINED end end
Context
该指令在<Directory>,<Files>或 htaccess context 中无效。
Ordering
当此脚本相对于其他模块运行时,可选的 arguments“early”或“late”控件。
LuaHookTypeChecker 指令
描述: | 为 type_checker 阶段的请求处理提供 hook |
句法: | LuaHookTypeChecker /path/to/lua/script.lua hook_function_name |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
该指令为请求处理的 type_checker 阶段提供 hook。此阶段是为请求分配 content type 和处理程序的位置,因此可用于根据输入修改类型和处理程序:
LuaHookTypeChecker "/path/to/lua/script.lua" type_checker
function type_checker(r) if r.uri:match("%.to_gif$") then -- match foo.png.to_gif r.content_type = "image/gif" -- assign it the image/gif type r.handler = "gifWizard" -- tell the gifWizard module to handle this r.filename = r.uri:gsub("%.to_gif$", "") -- fix the filename requested return apache2.OK end return apache2.DECLINED end
LuaInherit 指令
描述: | 控制 parent configuration 部分如何合并到子级中 |
句法: | LuaInherit none\|parent-first\|parent-last |
默认: | LuaInherit parent-first |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.4.0 及以后 |
默认情况下,如果在重叠的目录或位置配置部分中使用了 LuaHook *指令,则更具体部分中定义的脚本将在更通用部分(LuaInherit parent-first)中定义的脚本之后运行。您可以反转此 order,或使 parent context 完全不适用。
在之前的 2.3.x 版本中,默认值实际上是忽略来自 parent configuration 部分的 LuaHook *指令。
LuaInputFilter 指令
描述: | 为内容输入过滤提供 Lua function |
句法: | LuaInputFilter filter_name /path/to/lua/script.lua function_name |
Context: | 服务器配置 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.4.5 及以后 |
提供一种添加 Lua function 作为输入过滤器的方法。与输出过滤器一样,输入过滤器作为协同程序工作,首先在缓冲区发送之前产生,然后在需要向下传递链时产生,最后(可选地)产生需要附加到输入数据的任何内容。 global 变量bucket
在传递到 Lua 脚本时保存存储桶:
LuaInputFilter myInputFilter "/www/filter.lua" input_filter <Files "*.lua"> SetInputFilter myInputFilter </Files>
--[[ Example input filter that converts all POST data to uppercase. ]]-- function input_filter(r) print("luaInputFilter called") -- debug print coroutine.yield() -- Yield and wait for buckets while bucket do -- For each bucket, do... local output = string.upper(bucket) -- Convert all POST data to uppercase coroutine.yield(output) -- Send converted data down the chain end -- No more buckets available. coroutine.yield("&filterSignature=1234") -- Append signature at the end end
输入过滤器支持 denying/skipping 过滤器,如果它被认为是不需要的:
function input_filter(r) if not good then return -- Simply deny filtering, passing on the original content instead end coroutine.yield() -- wait for buckets ... -- insert filter stuff here end
有关详细信息,请参阅“使用 Lua 过滤器修改内容”。
LuaMapHandler 指令
描述: | Map 到 lua 处理程序的路径 |
句法: | LuaMapHandler uri-pattern /path/to/lua/script.lua[function-name] |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
该指令匹配 uri pattern 以调用特定文件中的特定处理程序 function。它使用 PCRE 正则表达式来匹配 uri,并支持在文件路径和 function name 中插入 match 组。小心编写正则表达式以避免安全问题。
例子:
LuaMapHandler "/(w+)/(w+)" "/scripts/$1.lua" "handle_$2"
这会匹配诸如/photos/show 之类的 uri 吗? id=9 到文件/scripts/photos.lua 并在_load 该文件后调用 lua vm 上的 handler function handle_show。
LuaMapHandler "/bingo" "/scripts/wombat.lua"
这将调用“handle”function,如果没有提供特定的 function name,这是默认值。
LuaOutputFilter 指令
描述: | 为内容输出过滤提供 Lua function |
句法: | LuaOutputFilter filter_name /path/to/lua/script.lua function_name |
Context: | 服务器配置 |
状态: | 延期 |
模块: | mod_lua |
兼容性: | 2.4.5 及以后 |
提供一种添加 Lua function 作为输出过滤器的方法。与输入过滤器一样,输出过滤器用作协同程序,首先在缓冲区发送之前产生,然后在需要向下传递链时产生,最后(可选地)产生需要附加到输入数据的任何内容。 global 变量bucket
在传递到 Lua 脚本时保存存储桶:
LuaOutputFilter myOutputFilter "/www/filter.lua" output_filter <Files "*.lua"> SetOutputFilter myOutputFilter </Files>
--[[ Example output filter that escapes all HTML entities in the output ]]-- function output_filter(r) coroutine.yield("(Handled by myOutputFilter)n") -- Prepend some data to the output, -- yield and wait for buckets. while bucket do -- For each bucket, do... local output = r:escape_html(bucket) -- Escape all output coroutine.yield(output) -- Send converted data down the chain end -- No more buckets available. end
与输入过滤器一样,输出过滤器支持 denying/skipping 过滤器,如果它被认为是不需要的:
function output_filter(r) if not r.content_type:match("text/html") then return -- Simply deny filtering, passing on the original content instead end coroutine.yield() -- wait for buckets ... -- insert filter stuff here end
Lua 过滤器 mod_filter
当 Lua 过滤器通过FilterProvider指令用作底层提供者时,过滤仅在 filter-name 与 provider-name 相同时才有效。
有关详细信息,请参阅“使用 Lua 过滤器修改内容”。
LuaPackageCPath 指令
描述: | 添加目录到 lua 的 package.cpath |
句法: | LuaPackageCPath /path/to/include/?.soa |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
添加 lua 的共享 library 搜索路径的路径。遵循与 lua 相同的约定。这只是在 lua vms 中的 package.cpath。
LuaPackagePath 指令
描述: | 添加目录到 lua 的 package.path |
句法: | LuaPackagePath /path/to/include/?.lua |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
添加 lua 模块搜索路径的路径。遵循与 lua 相同的约定。这只是在 lua vms 中的 package.path。
例子:
LuaPackagePath "/scripts/lib/?.lua" LuaPackagePath "/scripts/lib/?/init.lua"
LuaQuickHandler 指令
描述: | 为请求处理的快速处理程序提供 hook |
句法: | LuaQuickHandler /path/to/script.lua hook_function_name |
Context: | server config,virtual host |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
在将请求映射到 virtal host 之后,此阶段立即运行,并且可以用于在其他阶段启动之前执行某些请求处理,或者在不需要转换,map 到存储等的情况下提供请求。由于此阶段在其他任何事情之前都是 run,因此在此阶段中诸如<Location>或<Directory>之类的指令是无效的,就像尚未正确解析 URI 一样。
Context
该指令在<Directory>,<Files>或 htaccess context 中无效。
LuaRoot 指令
描述: | 指定解析 mod_lua 指令的相对_path 的基本路径 |
句法: | LuaRoot /path/to/a/directory |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
指定将用于评估 mod_lua 中所有相对_path 的基本路径。如果未指定,则将相对于当前工作目录解析它们,这对于服务器可能并不总是有效。
LuaScope 指令
描述: | 一次,请求,conn,线程-默认是一次 |
句法: | LuaScope once\|request\|conn\|thread\|server[min][max] |
默认: | LuaScope once |
Context: | server config,virtual host,directory,.htaccess |
覆盖: | 所有 |
状态: | 延期 |
模块: | mod_lua |
指定将在此“目录”中由处理程序使用的 Lua interpreter 的生命周期范围。默认为“一次”
- 一旦:
使用 interpreter 一次然后扔掉它。 - 请求:
使用 interpreter 根据此请求中的同一文件处理任何内容,这也是请求作用域。 - 康恩:
与请求相同但附加到 connection_rec - 线:
将 interpreter 用于处理请求的线程的生命周期(仅适用于线程 MPM)。 - 服务器:
这个与其他的不同,因为服务器范围非常长,并且多个线程将具有相同的 server_rec。为了适应这种情况,服务器范围的 Lua 状态存储在 apr 资源列表中。 min 和 max arguments 指定要保留在池中的最小和最大 Lua 状态数。
一般来说,thread
和server
范围的执行速度比 rest 快大约 2-3 倍,因为它们不必在每个请求上产生新的 Lua 状态(尤其是使用 event MPM,因为即使是 keepalive 请求也会为每个请求使用一个新线程)。如果您对脚本在重用 state 时没有问题感到满意,那么thread
或server
范围应该用于最大 performance。虽然thread
范围将提供最快的响应,但server
范围将使用较少的 memory,因为状态是合并的,允许 f.x。 1000 个线程只能共享 100 个 Lua 状态,因此只使用thread
范围所需的 memory 的 10%。