• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 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
    翻译 nameLuaHookTranslateName此阶段将请求的 URI 转换为系统上的文件名。诸如mod_alias和mod_rewrite的模块在此阶段运行。
    Map 到存储LuaHookMapToStorage此阶段 maps files 到其物理,缓存或 external/proxied 存储。它可以由代理或缓存模块使用
    检查访问权限LuaHookAccessChecker此阶段检查 client 是否可以访问资源。在用户通过身份验证之前,此阶段是 run,因此请注意。
    检查用户 IDLuaHookCheckUserID此阶段用于检查协商的用户 ID
    检查授权LuaHookAuthChecker或LuaAuthzProvider此阶段根据协商的凭据授权用户,例如用户 ID,客户端证书等。
    检查类型LuaHookTypeChecker此阶段检查所请求的文件并为其分配 content type 和处理程序
    修复程序LuaHookFixups这是内容处理程序运行之前的最终“修复任何问题”阶段。应在此处对请求进行任何 last-minute 更改。
    内容处理程序FX。.lua files 或通过LuaMapHandler这是处理内容的地方。 Files 被读取,解析,一些是 run,结果被发送到 client
    LoggingLuaHookLog处理完请求后,它会进入多个 logging 阶段,这些阶段会在错误或访问 log 中记录请求。 Mod_lua 能够 hook 到此开头并控制 logging 输出。

    Hook 函数作为唯一参数传递请求 object(LuaAuthzProvider 除外,它也从 Require 指令传递 arguments)。它们可以 return 任何 value,具体取决于 hook,但最常见的是它们 return OK,DONE 或 DECLINED,你可以在 Lua 中写为apache2.OKapache2.DONEapache2.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
    

    使用数据库时的注意事项

    尽管标准的queryrun函数是免费提供的,但建议您尽可能使用准备好的 statements 来优化 performance(如果您的 db 句柄持续 longtime)并最小化 SQL 注入攻击的风险。只有在没有变量插入语句(静态语句)时才应使用runquery。使用动态语句时,请使用db:preparedb: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 状态数。

    一般来说,threadserver范围的执行速度比 rest 快大约 2-3 倍,因为它们不必在每个请求上产生新的 Lua 状态(尤其是使用 event MPM,因为即使是 keepalive 请求也会为每个请求使用一个新线程)。如果您对脚本在重用 state 时没有问题感到满意,那么threadserver范围应该用于最大 performance。虽然thread范围将提供最快的响应,但server范围将使用较少的 memory,因为状态是合并的,允许 f.x。 1000 个线程只能共享 100 个 Lua 状态,因此只使用thread范围所需的 memory 的 10%。

    上篇:mod_logio

    下篇:mod_macro