Apache HTTP Server 2.4 版

| 描述 | 在 httpd 请求处理的各个部分提供 Lua 钩子 |
|---|---|
| 状态 | 扩展 |
| 模块标识符 | lua_module |
| 源文件 | mod_lua.c |
| 兼容性 | 2.3 及更高版本 |
此模块允许使用 Lua 编程语言编写的脚本扩展服务器。 mod_lua 提供的扩展点(钩子)包括许多可用于本地编译的 Apache HTTP Server 模块的钩子,例如将请求映射到文件、生成动态响应、访问控制、身份验证和授权
有关 Lua 编程语言的更多信息,请访问 Lua 网站。
此模块对 httpd 具有很大的控制权,这既是优势,也是潜在的安全风险。不建议您在与不信任的用户共享的服务器上使用此模块,因为它可能被滥用来更改 httpd 的内部工作方式。

LuaAuthzProvider
LuaCodeCache
LuaHookAccessChecker
LuaHookAuthChecker
LuaHookCheckUserID
LuaHookFixups
LuaHookInsertFilter
LuaHookLog
LuaHookMapToStorage
LuaHookPreTranslate
LuaHookTranslateName
LuaHookTypeChecker
LuaInherit
LuaInputFilter
LuaMapHandler
LuaOutputFilter
LuaPackageCPath
LuaPackagePath
LuaQuickHandler
LuaRoot
LuaScope基本模块加载指令是
LoadModule lua_module modules/mod_lua.so
mod_lua 提供了一个名为 lua-script 的处理程序,它可以与 SetHandler 或 AddHandler 指令一起使用
<Files "*.lua">
SetHandler lua-script
</Files>
这将导致 mod_lua 通过调用该文件的 handle 函数来处理以 .lua 结尾的文件的请求。
要获得更多灵活性,请参阅 LuaMapHandler。
在 Apache HTTP Server API 中,处理程序是一种特殊的钩子,负责生成响应。包含处理程序的模块示例包括 mod_proxy、mod_cgi 和 mod_status。
mod_lua 始终尝试调用 Lua 函数作为处理程序,而不是仅仅像 CGI 样式那样评估脚本主体。处理程序函数看起来像这样
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: %s\n", 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: %s\n", 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
此处理程序函数只是将 uri 或表单编码参数打印到纯文本页面。
这意味着(实际上鼓励)您可以在同一个脚本中拥有多个处理程序(或钩子或过滤器)。
mod_authz_core 提供了一个高级授权接口,它比直接使用相关钩子更容易使用。 Require 指令的第一个参数给出负责授权提供程序的名称。对于任何 Require 行,mod_authz_core 将调用给定名称的授权提供程序,并将该行的其余部分作为参数传递。然后,提供程序将检查授权并将结果作为返回值传递。
授权提供程序通常在身份验证之前调用。如果它需要知道已验证的用户名(或者用户是否将被验证),提供程序必须返回 apache2.AUTHZ_DENIED_NO_USER。这将导致身份验证继续进行,并第二次调用授权提供程序。
以下授权提供程序函数接受两个参数,一个 IP 地址和一个用户名。它将允许来自给定 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
以下配置将此函数注册为提供程序 foo 并为 URL / 配置它
LuaAuthzProvider foo authz_provider.lua authz_check_foo <Location "/"> Require foo 10.1.2.3 john_doe </Location>
钩子函数是模块(和 Lua 脚本)参与请求处理的方式。服务器公开的每种类型的钩子都存在于特定目的,例如将请求映射到文件系统、执行访问控制或设置 MIME 类型
| 钩子阶段 | mod_lua 指令 | 描述 |
|---|---|---|
| 快速处理程序 | LuaQuickHandler |
这是在请求映射到主机或虚拟主机后将调用的第一个钩子 |
| 预翻译名称 | LuaHookPreTranslateName |
此阶段在解码发生之前将请求的 URI 翻译成系统上的文件名。例如 mod_proxy 等模块可以在此阶段运行。 |
| 翻译名称 | LuaHookTranslateName |
此阶段将请求的 URI 翻译成系统上的文件名。例如 mod_alias 和 mod_rewrite 等模块在此阶段运行。 |
| 映射到存储 | LuaHookMapToStorage |
此阶段将文件映射到其物理、缓存或外部/代理存储。它可以被代理或缓存模块使用 |
| 检查访问 | LuaHookAccessChecker |
此阶段检查客户端是否可以访问资源。此阶段在用户进行身份验证之前运行,因此请注意。 |
| 检查用户 ID | LuaHookCheckUserID |
此阶段用于检查协商的用户 ID |
| 检查授权 | LuaHookAuthChecker 或 LuaAuthzProvider |
此阶段根据协商的凭据(例如用户 ID、客户端证书等)授权用户。 |
| 检查类型 | LuaHookTypeChecker |
此阶段检查请求的文件并为其分配内容类型和处理程序 |
| 修复 | LuaHookFixups |
这是在运行内容处理程序之前的最后一个“修复任何内容”阶段。应在此处对请求进行任何最后一刻的更改。 |
| 内容处理程序 | 例如 .lua 文件或通过 LuaMapHandler |
这是处理内容的地方。读取文件、解析文件、运行某些文件,并将结果发送到客户端 |
| 日志记录 | LuaHookLog |
处理请求后,它将进入多个日志记录阶段,这些阶段将请求记录在错误日志或访问日志中。Mod_lua 能够挂钩到此阶段的开始并控制日志记录输出。 |
钩子函数将请求对象作为其唯一参数传递(除了 LuaAuthzProvider,它还将从 Require 指令中传递参数)。它们可以返回任何值,具体取决于钩子,但最常见的是它们将返回 OK、DONE 或 DECLINED,您可以在 Lua 中将其写为 apache2.OK、apache2.DONE 或 apache2.DECLINED,或者 else an HTTP status 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 结构相同的字段,其中许多字段是可写的,也是可读的。(表字段的内容可以更改,但字段本身不能设置为不同的表。)
| 名称 | Lua 类型 | 可写 | 描述 |
|---|---|---|---|
allowoverrides |
字符串 | 否 | 应用于当前请求的 AllowOverride 选项。 |
ap_auth_type |
字符串 | 否 | 如果进行了身份验证检查,则将其设置为身份验证类型(例如 basic) |
args |
字符串 | 是 | 从请求中提取的查询字符串参数(例如 foo=bar&name=johnsmith) |
assbackwards |
布尔值 | 否 | 如果这是一个 HTTP/0.9 样式的请求(例如 GET /foo(没有标题)),则设置为 true |
auth_name |
字符串 | 否 | 用于授权的领域名称(如果适用)。 |
banner |
字符串 | 否 | 服务器横幅,例如 Apache HTTP Server/2.4.3 openssl/0.9.8c |
basic_auth_pw |
字符串 | 否 | 如果存在,则为随此请求发送的基本身份验证密码 |
canonical_filename |
字符串 | 否 | 请求的规范文件名 |
content_encoding |
字符串 | 否 | 当前请求的内容编码 |
content_type |
字符串 | 是 | 当前请求的内容类型,如类型检查阶段确定(例如 image/gif 或 text/html) |
context_prefix |
字符串 | 否 | |
context_document_root |
字符串 | 否 | |
document_root |
字符串 | 否 | 主机的文档根目录 |
err_headers_out |
表 | 否 | 响应的 MIME 标头环境,即使在错误时也会打印,并在内部重定向中保持一致。一个适合迭代的只读 lua 表格可作为 r:err_headers_out_table() 获得。 |
filename |
字符串 | 是 | 请求映射到的文件名,例如 /www/example.com/foo.txt。这可以在请求的预翻译名称、翻译名称或映射到存储阶段更改,以允许默认处理程序(或脚本处理程序)提供与请求不同的文件。 |
handler |
字符串 | 是 | 用于处理此请求的 处理程序 的名称,例如,如果要由 mod_lua 处理,则为 lua-script。这通常由 AddHandler 或 SetHandler 指令设置,但也可以通过 mod_lua 设置,以允许另一个处理程序处理否则不会由其处理的特定请求。 |
headers_in |
表 | 是 | 来自请求的 MIME 标头环境。这包含诸如 Host, User-Agent, Referer 等标头。一个适合迭代的只读 lua 表格可作为 r:headers_in_table() 使用。 |
headers_out |
表 | 是 | 响应的 MIME 标头环境。一个适合迭代的只读 lua 表格可作为 r:headers_out_table() 使用。 |
hostname |
字符串 | 否 | 主机名,由 Host: 标头或完整 URI 设置。 |
is_https |
布尔值 | 否 | 此请求是否通过 HTTPS 完成 |
is_initial_req |
布尔值 | 否 | 此请求是初始请求还是子请求 |
limit_req_body |
number | 否 | 此请求的请求主体的大小限制,如果无限制则为 0。 |
log_id |
字符串 | 否 | 用于在访问和错误日志中识别请求的 ID。 |
method |
字符串 | 否 | 请求方法,例如 GET 或 POST。 |
notes |
表 | 是 | 可以从一个模块传递到另一个模块的注释列表。一个适合迭代的只读 lua 表格可作为 r:notes_table() 使用。 |
options |
字符串 | 否 | 应用于当前请求的 Options 指令。 |
path_info |
字符串 | 否 | 从此请求中提取的 PATH_INFO。 |
port |
number | 否 | 请求使用的服务器端口。 |
protocol |
字符串 | 否 | 使用的协议,例如 HTTP/1.1 |
proxyreq |
字符串 | 是 | 表示这是否是代理请求。此值通常在请求的 post_read_request/pre_translate_name/translate_name 阶段设置。 |
range |
字符串 | 否 | Range: 标头的内容。 |
remaining |
number | 否 | 要从请求主体读取的剩余字节数。 |
server_built |
字符串 | 否 | 服务器可执行文件构建的时间。 |
server_name |
字符串 | 否 | 此请求的服务器名称。 |
some_auth_required |
布尔值 | 否 | 此请求是否需要一些授权。 |
subprocess_env |
表 | 是 | 为此请求设置的环境变量。一个适合迭代的只读 lua 表格可作为 r:subprocess_env_table() 使用。 |
started |
number | 否 | 服务器(重新)启动的时间,以自纪元(1970 年 1 月 1 日)以来的秒数表示。 |
status |
number | 是 | 此请求的(当前)HTTP 返回代码,例如 200 或 404。 |
the_request |
字符串 | 否 | 客户端发送的请求字符串,例如 GET /foo/bar HTTP/1.1。 |
unparsed_uri |
字符串 | 否 | 请求的未解析 URI |
uri |
字符串 | 是 | URI 在被 httpd 解析后。 |
user |
字符串 | 是 | 如果已进行身份验证检查,则设置为已验证用户的名称。 |
useragent_ip |
字符串 | 否 | 发出请求的用户代理的 IP |
request_rec 对象至少具有以下方法
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 bla\n") -- 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 processa: 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: %s\n"):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 %s\n"):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
-- 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 的包可用,至少包含以下内容。
mod_proxy 使用的内部常量mod_authz_core 使用的内部常量(其他 HTTP 状态代码尚未实现。)
通过 LuaInputFilter 或 LuaOutputFilter 实现的过滤器函数被设计为使用协程的三阶段非阻塞函数,以暂停和恢复函数,因为存储桶被发送到过滤器链中。此类函数的核心结构是
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 altogether:
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 为查询和运行最流行的数据库引擎(mySQL、PostgreSQL、FreeTDS、ODBC、SQLite、Oracle)以及 mod_dbd 上的命令实现了一个简单的数据库功能。
要作为 dbacquire 的第一个参数使用的 dbType 区分大小写。
它应该是 mysql、pgsql、freetds、odbc、sqlite2、sqlite3、oracle 或 mod_dbd 之一。
以下示例展示了如何获取数据库句柄并从表中返回信息
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<br/>", 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")
由 dbacquire 返回的数据库对象具有以下方法
从数据库中进行正常的 select 和查询
-- 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()
由 db:select 或通过 db:prepare 创建的预处理语句函数返回的结果集可用于同步或异步获取行,具体取决于指定的行号
result(0) 以同步方式获取所有行,返回一个行表格。
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.
可以构造一个函数,该函数返回一个迭代函数,以同步或异步方式迭代所有行,具体取决于 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 函数可自由使用,但建议您尽可能使用预处理语句,以优化性能(如果您的 db 句柄持续很长时间)并最大限度地降低 SQL 注入攻击的风险。run 和 query 仅应在没有将变量插入语句(静态语句)时使用。使用动态语句时,请使用 db:prepare 或 db:prepared。
| 描述 | 将授权提供程序函数插入 mod_authz_core |
|---|---|
| 语法 | LuaAuthzProvider provider_name /path/to/lua/script.lua function_name |
| 上下文 | 服务器配置 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 2.4.3 及更高版本 |
在将 lua 函数注册为授权提供程序后,可以使用 Require 指令使用它
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 stat|forever|never |
| 默认值 | LuaCodeCache stat |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
指定内存中代码缓存的行为。默认值为 stat,它在每次需要该文件时都会统计顶级脚本(而不是任何包含的脚本),如果修改时间表明它比已加载的脚本更新,则会重新加载它。其他值会导致它永远缓存文件(不统计和替换)或永远不缓存文件。
通常,stat 或 forever 适用于生产环境,而 stat 或 never 适用于开发环境。
LuaCodeCache stat LuaCodeCache forever LuaCodeCache never
| 描述 | 为请求处理的 access_checker 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookAccessChecker /path/to/lua/script.lua hook_function_name [early|late] |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 可选的第三个参数在 2.3.15 及更高版本中受支持 |
将您的钩子添加到 access_checker 阶段。访问检查器钩子函数通常返回 OK、DECLINED 或 HTTP_FORBIDDEN。
可选参数“early”或“late”控制此脚本相对于其他模块的运行时间。
| 描述 | 为请求处理的 auth_checker 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookAuthChecker /path/to/lua/script.lua hook_function_name [early|late] |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 可选的第三个参数在 2.3.15 及更高版本中受支持 |
在处理请求的 auth_checker 阶段调用 lua 函数。这可用于实现任意身份验证和授权检查。一个非常简单的示例
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
可选参数“early”或“late”控制此脚本相对于其他模块的运行时间。
| 描述 | 为请求处理的 check_user_id 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookCheckUserID /path/to/lua/script.lua hook_function_name [early|late] |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 可选的第三个参数在 2.3.15 及更高版本中受支持 |
...
可选参数“early”或“late”控制此脚本相对于其他模块的运行时间。
| 描述 | 为请求处理的 fixups 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookFixups /path/to/lua/script.lua hook_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
与 LuaHookTranslateName 相同,但在 fixups 阶段执行
| 描述 | 为请求处理的 insert_filter 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookInsertFilter /path/to/lua/script.lua hook_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
尚未实现
| 描述 | 为请求处理的访问日志阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookLog /path/to/lua/script.lua log_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
这个简单的日志记录钩子允许您在 httpd 进入请求的日志记录阶段时运行一个函数。使用它,您可以将数据追加到您自己的日志中,在写入常规日志之前操作数据,或阻止创建日志条目。要阻止通常的日志记录发生,只需在您的日志记录处理程序中返回 apache2.DONE,否则返回 apache2.OK 以告诉 httpd 按正常方式记录。
示例
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
| 描述 | 为请求处理的 map_to_storage 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookMapToStorage /path/to/lua/script.lua hook_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
与 LuaHookTranslateName 相同,但在请求的 map-to-storage 阶段执行。诸如 mod_cache 之类的模块在此阶段运行,这为这里要做什么提供了一个有趣的示例
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
| 描述 | 为请求处理的 pre_translate 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookPreTranslate /path/to/lua/script.lua hook_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
与 LuaHookTranslateName 相同,但在 pre_translate 阶段执行,此时 URI-path 未百分比解码。
| 描述 | 在请求处理的翻译名称阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookTranslateName /path/to/lua/script.lua hook_function_name [early|late] |
| 上下文 | 服务器配置,虚拟主机 |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 可选的第三个参数在 2.3.15 及更高版本中受支持 |
在请求处理的翻译名称阶段添加一个钩子(在 APR_HOOK_MIDDLE 处)。钩子函数接收一个参数,即 request_rec,并应返回一个状态码,该状态码可以是 HTTP 错误代码,也可以是 apache2 模块中定义的常量:apache2.OK、apache2.DECLINED 或 apache2.DONE。
对于那些不熟悉钩子的人来说,基本上每个钩子都会被调用,直到其中一个返回 apache2.OK。如果你的钩子不想进行翻译,它应该只返回 apache2.DECLINED。如果请求应该停止处理,则返回 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
此指令在 <Directory>、<Files> 或 htaccess 上下文中无效。
可选参数“early”或“late”控制此脚本相对于其他模块的运行时间。
| 描述 | 在请求处理的 type_checker 阶段提供一个钩子 |
|---|---|
| 语法 | LuaHookTypeChecker /path/to/lua/script.lua hook_function_name |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
此指令为请求处理的 type_checker 阶段提供一个钩子。此阶段是请求被分配内容类型和处理程序的地方,因此可以用来根据输入修改类型和处理程序。
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 none|parent-first|parent-last |
| 默认值 | LuaInherit parent-first |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 2.4.0 及更高版本 |
默认情况下,如果 LuaHook* 指令在重叠的 Directory 或 Location 配置节中使用,则在更通用的节中定义的脚本将在更具体的节中定义的脚本之后运行(LuaInherit parent-first)。你可以反转此顺序,或者使父上下文根本不适用。
在之前的 2.3.x 版本中,默认情况下实际上是忽略父配置节中的 LuaHook* 指令。
| 描述 | 为内容输入过滤提供一个 Lua 函数 |
|---|---|
| 语法 | LuaInputFilter filter_name /path/to/lua/script.lua function_name |
| 上下文 | 服务器配置 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 2.4.5 及更高版本 |
提供了一种将 Lua 函数添加为输入过滤器的方法。与输出过滤器一样,输入过滤器作为协程工作,首先在发送缓冲区之前产生,然后在需要将桶传递到链中时产生,最后(可选)产生需要附加到输入数据的任何内容。全局变量 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
输入过滤器支持拒绝/跳过过滤器,如果它被认为是不需要的。
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 过滤器修改内容”。
| 描述 | 将路径映射到 lua 处理程序 |
|---|---|
| 语法 | LuaMapHandler uri-pattern /path/to/lua/script.lua [function-name] |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
此指令将 uri 模式与在特定文件中调用特定处理程序函数相匹配。它使用 PCRE 正则表达式来匹配 uri,并支持将匹配组插入到文件路径和函数名中。在编写正则表达式时要小心,避免安全问题。
LuaMapHandler "/(\w+)/(\w+)" "/scripts/$1.lua" "handle_$2"
这将匹配诸如 /photos/show?id=9 之类的 uri 到文件 /scripts/photos.lua,并在加载该文件后在 lua vm 上调用处理程序函数 handle_show。
LuaMapHandler "/bingo" "/scripts/wombat.lua"
这将调用“handle”函数,如果未提供特定函数名,则为默认函数。
| 描述 | 为内容输出过滤提供一个 Lua 函数 |
|---|---|
| 语法 | LuaOutputFilter filter_name /path/to/lua/script.lua function_name |
| 上下文 | 服务器配置 |
| 状态 | 扩展 |
| 模块 | mod_lua |
| 兼容性 | 2.4.5 及更高版本 |
提供了一种将 Lua 函数添加为输出过滤器的方法。与输入过滤器一样,输出过滤器作为协程工作,首先在发送缓冲区之前产生,然后在需要将桶传递到链中时产生,最后(可选)产生需要附加到输入数据的任何内容。全局变量 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)<br/>\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
与输入过滤器一样,输出过滤器支持拒绝/跳过过滤器,如果它被认为是不需要的。
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
mod_filter 的 Lua 过滤器当 Lua 过滤器通过 FilterProvider 指令作为底层提供程序使用时,过滤仅在 filter-name 与 provider-name 相同的情况下才有效。
有关更多信息,请参见“使用 Lua 过滤器修改内容”。
| 描述 | 将目录添加到 lua 的 package.cpath 中 |
|---|---|
| 语法 | LuaPackageCPath /path/to/include/?.soa |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
将路径添加到 lua 的共享库搜索路径中。遵循与 lua 相同的约定。这只是在 lua vm 中修改 package.cpath。
| 描述 | 将目录添加到 lua 的 package.path 中 |
|---|---|
| 语法 | LuaPackagePath /path/to/include/?.lua |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
将路径添加到 lua 的模块搜索路径中。遵循与 lua 相同的约定。这只是在 lua vm 中修改 package.path。
LuaPackagePath "/scripts/lib/?.lua" LuaPackagePath "/scripts/lib/?/init.lua"
| 描述 | 在请求处理的快速处理程序中提供一个钩子 |
|---|---|
| 语法 | LuaQuickHandler /path/to/script.lua hook_function_name |
| 上下文 | 服务器配置,虚拟主机 |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
此阶段在请求映射到虚拟主机后立即运行,可用于在其他阶段开始之前执行一些请求处理,或在无需翻译、映射到存储等的情况下提供服务。由于此阶段在其他任何操作之前运行,因此诸如 <Location> 或 <Directory> 之类的指令在此阶段无效,就像 URI 尚未正确解析一样。
此指令在 <Directory>、<Files> 或 htaccess 上下文中无效。
| 描述 | 指定用于解析 mod_lua 指令的相对路径的基路径 |
|---|---|
| 语法 | LuaRoot /path/to/a/directory |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
指定将用于评估 mod_lua 中所有相对路径的基路径。如果未指定,它们将相对于当前工作目录解析,这可能并不总是对服务器有效。
| 描述 | once、request、conn、thread 之一 - 默认值为 once |
|---|---|
| 语法 | LuaScope once|request|conn|thread|server [min] [max] |
| 默认值 | LuaScope once |
| 上下文 | 服务器配置、虚拟主机、目录、.htaccess |
| 覆盖 | 全部 |
| 状态 | 扩展 |
| 模块 | mod_lua |
指定将由此“目录”中的处理程序使用的 Lua 解释器的生命周期范围。默认值为“once”。
min 和 max 参数指定要保留在池中的 Lua 状态的最小值和最大值。一般来说,thread 和 server 范围的执行速度比其他范围快 2-3 倍,因为它们不必在每个请求上都生成新的 Lua 状态(尤其是在事件 MPM 中,因为即使是保持活动连接的请求也会为每个请求使用一个新线程)。如果你确信你的脚本在重用状态时不会出现问题,那么应该使用 thread 或 server 范围来获得最佳性能。虽然 thread 范围将提供最快的响应,但 server 范围将使用更少的内存,因为状态是池化的,允许例如 1000 个线程共享 100 个 Lua 状态,从而仅使用 thread 范围所需内存的 10%。