apache / 2.4 / reference / mod-mod_lua.html

Apache 模块 mod_lua

Description: 提供 Lua 钩子到 httpd 请求处理的各个部分
Status: Extension
Module Identifier: lua_module
Source File: mod_lua.c
Compatibility: 2 .3 及更高版本

Summary

该模块允许使用以 Lua 编程语言编写的脚本扩展服务器。 mod_lua可用的扩展点(钩子)包括许多本机编译的 Apache HTTP Server 模块可用的钩子,例如将请求 Map 到文件,生成动态响应,访问控制,身份验证和授权

可以在Lua 网站上找到有关 Lua 编程语言的更多信息。

Warning

该模块具有对 httpd 的强大控制权,这既有优势,又有潜在的安全风险。不建议您在不信任的用户共享的服务器上使用此模块,因为它可能会被用来更改 httpd 的内部工作方式。

Basic Configuration

基本的模块加载指令是

LoadModule lua_module modules/mod_lua.so

mod_lua提供了一个名为lua-script的处理程序,可以与SetHandlerAddHandler指令一起使用:

<Files "*.lua">
    SetHandler lua-script
</Files>

这将导致mod_lua通过调用该文件的handle函数来处理对以.lua结尾的文件的请求。

要获得更大的灵 Active,请参见LuaMapHandler

Writing Handlers

在 Apache HTTP Server API 中,处理程序是负责生成响应的特定类型的钩子。包括处理程序的模块示例为mod_proxymod_cgimod_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将调用给定名称的授权提供程序,并将其余行作为参数传递。然后,提供者将检查授权并将结果作为返回值传递。

通常在身份验证之前调用 authz 提供程序。如果需要知道经过身份验证的用户名(或者完全要对用户进行身份验证),则提供程序必须返回apache2.AUTHZ_DENIED_NO_USER。这将导致身份验证 continue 进行,并再次调用 authz 提供程序。

以下 authz 提供程序功能使用两个参数,一个 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>

Writing Hooks

钩子函数是模块(和 Lua 脚本)如何参与请求的处理。服务器公开的每种钩子类型都有特定的用途,例如将请求 Map 到文件系统,执行访问控制或设置 mime 类型:

Hook phase mod_lua directive Description
Quick handler LuaQuickHandler 这是将请求 Map 到主机或虚拟主机后将调用的第一个钩子
Translate name LuaHookTranslateName 此阶段将请求的 URI 转换为系统上的文件名。 mod_aliasmod_rewrite之类的模块在此阶段运行。
Map 到存储 LuaHookMapToStorage 此阶段将文件 Map 到其物理,缓存或外部/代理存储。可以由代理或缓存模块使用
Check Access LuaHookAccessChecker 此阶段检查 Client 端是否有权访问资源。请在验证用户身份之前运行此阶段。
检查用户 ID LuaHookCheckUserID 此阶段用于检查协商的用户标识
Check Authorization LuaHookAuthCheckerLuaAuthzProvider 此阶段根据协商的凭证(例如用户 ID,Client 端证书等)授权用户。
Check Type LuaHookTypeChecker 此阶段检查请求的文件,并为其分配 Content Type 和处理程序
Fixups LuaHookFixups 这是内容处理程序运行之前的最后一个“修复所有内容”阶段。对请求的任何最新更改都应在此处进行。
Content handler fx。 .lua个文件或通过LuaMapHandler 这是处理内容的地方。读取,解析文件,运行一些文件,并将结果发送到 Client 端
Logging LuaHookLog 处理完请求后,它将进入几个日志记录阶段,这些阶段会将请求记录在错误或访问日志中。 Mod_lua 可以加入该程序的开头并控制日志记录输出。

钩子函数作为唯一的参数传递给请求对象(LuaAuthzProvider 除外,该函数也从 Require 指令传递参数)。它们可以返回任何值,具体取决于钩子,但是最常见的是,它们将返回 OK,DONE 或 DECLINED,您可以在 Lua 中将它们写为apache2.OKapache2.DONEapache2.DECLINED,或者是 HTTP 状态代码。

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

Data Structures

  • request_rec

    • request_rec 作为用户数据 Map。它具有一个元表,可让您使用它做有用的事情。在大多数情况下,它具有与 request_rec 结构相同的字段,其中许多字段可写且可读。 (表字段的内容可以更改,但是字段本身不能设置为其他表.)
Name Lua type Writable Description
allowoverrides string no AllowOverride 选项应用于当前请求。
ap_auth_type string no 如果进行了身份验证检查,则将其设置为身份验证类型(f.x. basic)
args string yes 从请求中提取的查询字符串参数(f.x. foo=bar&name=johnsmith)
assbackwards boolean no 如果这是 HTTP/0.9 样式的请求(例如GET /foo(无标题)),则设置为 true
auth_name string no 用于授权的领域名称(如果适用)。
banner string no 服务器标题 f.x。 Apache HTTP Server/2.4.3 openssl/0.9.8c
basic_auth_pw string no 与此请求一起发送的基本身份验证密码(如果有)
canonical_filename string no 请求的规范文件名
content_encoding string no 当前请求的内容编码
content_type string yes 在 type_check 阶段确定的当前请求的 Content Type(f.x. image/giftext/html)
context_prefix string no
context_document_root string no
document_root string no 主机的文档根目录
err_headers_out table no 响应的 MIME Headers 环境,即使在错误上也可以打印,并在内部重定向中保持不变。适用于迭代的只读 lua 表可作为 r:err_headers_out_table()获得。
filename string yes 请求 Map 到的文件名 f.x。 /www/example.com/foo.txt。可以在请求的翻译名称或 Map 到存储阶段更改此设置,以允许默认处理程序(或脚本处理程序)为不同于请求的文件提供服务。
handler string yes 应该为该请求提供服务的handler的名称,f.x。 lua-script(如果要由 mod_lua 提供)。这通常由AddHandlerSetHandler指令设置,但也可以通过 mod_lua 设置,以允许另一个处理程序处理特定的请求,否则该请求将无法处理。
headers_in table yes 来自请求的 MIME Headers 环境。其中包含诸如Host, User-Agent, Referer之类的标题。适用于迭代的只读 lua 表可作为 r:headers_in_table()获得。
headers_out table yes 响应的 MIME Headers 环境。 r:headers_out_table()提供了适用于迭代的只读 lua 表。
hostname string no Host:Headers 或完整 URI 设置的主机名。
is_https boolean no 此请求是否通过 HTTPS 完成
is_initial_req boolean no 此请求是初始请求还是子请求
limit_req_body number no 此请求的请求主体的大小限制;如果没有限制,则为 0.
log_id string no 标识访问和错误日志中的请求的 ID。
method string no 请求方法,f.x。 GETPOST
notes table yes 可以从一个模块传递到另一个模块的 Comments 列表。适用于迭代的只读 lua 表可作为 r:notes_table()获得。
options string no 选项指令应用于当前请求。
path_info string no 从此请求中提取的 PATH_INFO。
port number no 请求使用的服务器端口。
protocol string no 使用的协议,f.x。 HTTP/1.1
proxyreq string yes 表示这是否是代理请求。通常在请求的 post_read_request/translate_name 阶段中设置此值。
range string no Range:Headers 的内容。
remaining number no 要从请求主体读取的剩余字节数。
server_built string no 服务器可执行文件的构建时间。
server_name string no 此请求的服务器名称。
some_auth_required boolean no 此请求是否需要某种授权。
subprocess_env table yes 为此请求设置的环境变量。 r:subprocess_env_table()提供了适用于迭代的只读 lua 表。
started number no 从该时期(1970 年 1 月 1 日)开始(重新)启动服务器的时间(以秒为单位)
status number yes 此请求的(当前)HTTP 返回代码 f.x。 200404
the_request string no Client 端发送的请求字符串 f.x。 GET /foo/bar HTTP/1.1
unparsed_uri string no 请求的未解析的 URI
uri string yes httpd 解析后的 URI
user string yes 如果进行了身份验证检查,则将其设置为已验证用户的名称。
useragent_ip string no 发出请求的用户代理的 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 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: %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

Logging Functions

-- 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 Package

名为apache2的软件包可(至少)包含以下内容。

  • apache2.OK

    • 内部常量确定。如果处理程序处理了请求,则应返回此值。
  • apache2.DECLINED

    • 内部常量 DECLINED。如果处理程序不打算处理请求,则应返回此值。
  • apache2.DONE

    • 内部常量 DONE。
  • apache2.version

    • Apache HTTP 服务器版本字符串
  • apache2.HTTP_MOVED_TEMPORARILY

    • HTTP 状态码
  • apache2.PROXYREQ_NONE,apache2.PROXYREQ_PROXY,apache2.PROXYREQ_REVERSE,apache2.PROXYREQ_RESPONSE

  • apache2.AUTHZ_DENIED,apache2.AUTHZ_GRANTED,apache2.AUTHZ_NEUTRAL,apache2.AUTHZ_GENERAL_ERROR,apache2.AUTHZ_DENIED_NO_USER

(其他 HTTP 状态代码尚未实现.)

使用 Lua 过滤器修改内容

通过LuaInputFilterLuaOutputFilter实现的过滤器功能被设计为三级非阻塞函数,使用协程在存储桶沿着过滤器链向下发送时挂起和恢复该函数。该功能的核心结构是:

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

Database connectivity

Mod_lua 实现了一种简单的数据库功能,用于在最流行的数据库引擎(mySQL,PostgreSQL,FreeTDS,ODBC,SQLite,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
", 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返回的数据库对象具有以下方法:

从数据库中正常选择和查询:

-- 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创建的 prepared 语句函数返回的结果集可用于同步或异步获取行,具体取决于指定的行号:
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

使用数据库时的注意事项

尽管可以免费使用标准的queryrun函数,但是建议您尽可能使用准备好的语句,以优化性能(如果您的数据库句柄可以长期使用)并最大程度地降低 SQL 注入攻击的风险。 runquery仅在没有变量插入到语句(静态语句)中时使用。使用动态语句时,请使用db:preparedb:prepared

LuaAuthzProvider Directive

Description: 将授权提供程序功能插入mod_authz_core
Syntax: LuaAuthzProvider provider_name /path/to/lua/script.lua function_name
Context: server config
Status: Extension
Module: mod_lua
Compatibility: 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 Directive

Description: 配置编译后的代码缓存。
Syntax: LuaCodeCache stat|forever|never
Default: LuaCodeCache stat
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

指定内存中代码缓存的行为。默认值为 stat,它在每次需要该文件时统计顶级脚本(不包含任何脚本),如果修改后的时间表明该文件比已加载的文件新,则重新加载该脚本。其他值使它永久保留文件缓存(不统计和替换)或从不缓存文件。

通常,统计数据或永远对生产有利,而统计数据或永不对 Developing 有利。

Examples:

LuaCodeCache stat
LuaCodeCache forever
LuaCodeCache never

LuaHookAccessChecker Directive

Description: 为请求处理的 access_checker 阶段提供一个钩子
Syntax: LuaHookAccessChecker /path/to/lua/script.lua hook_function_name [early|late]
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua
Compatibility: 2.3.15 和更高版本支持可选的第三个参数

将您的钩子添加到 access_checker 阶段。访问检查器钩子函数通常返回 OK,DECLINED 或 HTTP_FORBIDDEN。

Ordering

可选参数“ early”或“ late”控制此脚本相对于其他模块运行的时间。

LuaHookAuthChecker Directive

Description: 为请求处理的 auth_checker 阶段提供一个钩子
Syntax: LuaHookAuthChecker /path/to/lua/script.lua hook_function_name [early|late]
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua
Compatibility: 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

Ordering

可选参数“ early”或“ late”控制此脚本相对于其他模块运行的时间。

LuaHookCheckUserID Directive

Description: 为请求处理的 check_user_id 阶段提供一个钩子
Syntax: LuaHookCheckUserID /path/to/lua/script.lua hook_function_name [early|late]
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua
Compatibility: 2.3.15 和更高版本支持可选的第三个参数

...

Ordering

可选参数“ early”或“ late”控制此脚本相对于其他模块运行的时间。

LuaHookFixups Directive

Description: 为请求处理的修正阶段提供一个钩子
Syntax: LuaHookFixups /path/to/lua/script.lua hook_function_name
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

就像 LuaHookTranslateName 一样,但是在修正阶段执行

LuaHookInsertFilter Directive

Description: 为请求处理的 insert_filter 阶段提供一个钩子
Syntax: LuaHookInsertFilter /path/to/lua/script.lua hook_function_name
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

尚未实现

LuaHookLog Directive

Description: 为请求处理的访问日志阶段提供一个钩子
Syntax: LuaHookLog /path/to/lua/script.lua log_function_name
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

这个简单的日志钩子可让您在 httpd 进入请求的日志记录阶段时运行功能。使用它,您可以将数据追加到自己的日志中,可以在写入常规日志之前处理数据,或者阻止创建日志条目。为了防止发生通常的日志记录,只需在日志记录处理程序中返回apache2.DONE,否则返回apache2.OK告诉 httpd 正常记录。

Example:

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 Directive

Description: 为请求处理的 map_to_storage 阶段提供一个钩子
Syntax: LuaHookMapToStorage /path/to/lua/script.lua hook_function_name
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

类似于LuaHookTranslateName,但在请求的 Map 到存储阶段执行。诸如 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

LuaHookTranslateName Directive

Description: 为请求处理的翻译名称阶段提供一个钩子
Syntax: LuaHookTranslateName /path/to/lua/script.lua hook_function_name [early|late]
Context: 服务器配置,虚拟主机
Override: All
Status: Extension
Module: mod_lua
Compatibility: 2.3.15 和更高版本支持可选的第三个参数

在请求处理的翻译名称阶段添加一个钩子(位于 APR_HOOK_MIDDLE)。 hook 函数接收单个参数 request_rec,并应返回状态代码,该状态代码可以是 HTTP 错误代码,也可以返回 apache2 模块中定义的常量:apache2.OK,apache2.DECLINED 或 apache2.DONE。

对于那些新的钩子,基本上每个钩子都会被调用,直到其中一个返回 apache2.OK。如果您的钩子不想执行翻译,则应仅返回 apache2.DECLINED。如果请求应停止处理,则返回 apache2.DONE。

Example:

# 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 上下文中无效。

Ordering

可选参数“ early”或“ late”控制此脚本相对于其他模块运行的时间。

LuaHookTypeChecker Directive

Description: 为请求处理的 type_checker 阶段提供一个钩子
Syntax: LuaHookTypeChecker /path/to/lua/script.lua hook_function_name
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

该指令为请求处理的 type_checker 阶段提供了一个钩子。在此阶段,为请求分配了 Content Type 和处理程序,因此可用于根据 Importing 来修改类型和处理程序:

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 Directive

Description: 控制父配置节如何合并到子级中
Syntax: LuaInherit none|parent-first|parent-last
Default: LuaInherit parent-first
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua
Compatibility: 2 .4.0 及更高版本

默认情况下,如果在重叠的“目录”或“位置”配置节中使用 LuaHook *指令,则在“更具体”节中定义的脚本之后(在“更通用”节中定义的脚本之后运行)(LuaInherit parent-first)。您可以颠倒此 Sequences,或使父上下文根本不适用。

在以前的 2.3.x 版本中,默认设置实际上是忽略父配置节中的 LuaHook *指令。

LuaInputFilter Directive

Description: 提供用于内容 Importing 过滤的 Lua 功能
Syntax: LuaInputFilter filter_name /path/to/lua/script.lua function_name
Context: server config
Status: Extension
Module: mod_lua
Compatibility: 2 .4.5 及更高版本

提供一种添加 Lua 函数作为 Importing 过滤器的方法。与输出过滤器一样,Importing 过滤器充当协程,首先在发送缓冲区之前屈服,然后在需要将存储桶沿链向下传递时屈服,最后(可选)产生需要附加到 Importing 数据的任何内容。全局变量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

如果认为不需要过滤器,Importing 过滤器支持拒绝/跳过过滤器:

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 Directive

Description: 将路径 Map 到 lua 处理程序
Syntax: LuaMapHandler uri-pattern /path/to/lua/script.lua [function-name]
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

该指令与 uri 模式匹配,以调用特定文件中的特定处理函数。它使用 PCRE 正则表达式来匹配 uri,并支持将匹配组内插到文件路径和函数名称中。小心编写正则表达式,以避免安全问题。

Examples:

LuaMapHandler "/(\w+)/(\w+)" "/scripts/$1.lua" "handle_$2"

这会将 uri(例如/ photos/show?id = 9)与文件/scripts/photos.lua 匹配,并在加载该文件后在 lua vm 上调用处理函数 handle_show。

LuaMapHandler "/bingo" "/scripts/wombat.lua"

这将调用“句柄”功能,如果未提供特定的功能名称,则为默认功能。

LuaOutputFilter Directive

Description: 提供 Lua 功能以进行内容输出过滤
Syntax: LuaOutputFilter filter_name /path/to/lua/script.lua function_name
Context: server config
Status: Extension
Module: mod_lua
Compatibility: 2 .4.5 及更高版本

提供一种添加 Lua 函数作为输出过滤器的方法。与 Importing 过滤器一样,输出过滤器充当协程,首先在发送缓冲区之前产生,然后在需要将存储桶沿链向下传递时产生,最后(可选)产生需要附加到 Importing 数据的任何内容。全局变量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

与 Importing 过滤器一样,输出过滤器支持拒绝/跳过被认为是不需要的过滤器:

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 filters with mod_filter

当通过FilterProvider指令将 Lua 过滤器用作基础提供程序时,仅当过滤器名称与提供程序名称相同时才进行过滤。

有关更多信息,请参见“ 用 Lua 过滤器修改内容”。

LuaPackageCPath Directive

Description: 在 lua 的 package.cpath 中添加一个目录
Syntax: LuaPackageCPath /path/to/include/?.soa
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

将路径添加到 lua 的共享库搜索路径。遵循与 lua 相同的约定。这只是在 lua vms 中修改了 package.cpath。

LuaPackagePath Directive

Description: 在 lua 的 package.path 中添加一个目录
Syntax: LuaPackagePath /path/to/include/?.lua
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

将路径添加到 lua 的模块搜索路径。遵循与 lua 相同的约定。这只是在 lua vms 中修改了 package.path。

Examples:

LuaPackagePath "/scripts/lib/?.lua"
LuaPackagePath "/scripts/lib/?/init.lua"

LuaQuickHandler Directive

Description: 为请求处理的快速处理程序提供一个钩子
Syntax: LuaQuickHandler /path/to/script.lua hook_function_name
Context: 服务器配置,虚拟主机
Override: All
Status: Extension
Module: mod_lua

该阶段在将请求 Map 到病毒宿主后立即运行,可用于在其他阶段启动之前执行一些请求处理,或用于服务请求而无需翻译,Map 到存储等。由于此阶段先于其他任何阶段运行,因此诸如<Location><Directory>之类的指令在此阶段无效,就像尚未正确解析 URI 一样。

Context

该指令在<Directory><Files>或 htaccess 上下文中无效。

LuaRoot Directive

Description: 指定用于解析 mod_lua 指令的相对路径的基本路径
Syntax: LuaRoot /path/to/a/directory
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

指定将用于评估 mod_lua 中所有相对路径的基本路径。如果未指定,它们将相对于当前工作目录进行解析,这对于服务器而言可能并不总是很好。

LuaScope Directive

Description: 一次,request,conn,thread 之一-默认为一次
Syntax: LuaScope once|request|conn|thread|server [min] [max]
Default: LuaScope once
Context: 服务器配置,虚拟主机,目录,.htaccess
Override: All
Status: Extension
Module: mod_lua

在此“目录”中,指定处理程序将使用的 Lua 解释器的生命周期范围。默认值为“一次”

  • once:

    • 一次使用解释器,将其丢弃。
  • request:

    • 使用解释器来处理基于此请求中相同文件的任何内容,该文件也属于请求范围。
  • conn:

    • 与请求相同,但附加到 connection_rec
  • thread:

    • 在处理请求的线程的生存期内使用解释器(仅适用于线程 MPM)。
  • server:

    • 此服务器与其他服务器有所不同,因为服务器范围的寿命很长,并且多个线程将具有相同的 server_rec。为了适应这种情况,服务器范围的 Lua 状态存储在 apr 资源列表中。 minmax参数指定要保留在池中的 Lua 状态的最小和最大数目。

一般而言,threadserver范围的执行速度比其余范围快 2-3 倍,因为它们不必在每个请求上都产生新的 Lua 状态(尤其是在事件 MPM 时,因为即使 keepalive 请求也将使用新线程来执行)每个请求)。如果您对脚本重用状态不会感到满意,则应使用threadserver范围以实现最佳性能。 thread范围将提供最快的响应,而server范围将使用较少的内存,因为状态被合并,从而允许 f.x。 1000 个线程仅共享 100 个 Lua 状态,因此仅使用thread范围所需的内存的 10%。