Lua API
Lua 可以用于生成配置(类似于 include_shell
的快捷方式)或编写实际的响应处理程序。
使用 Lua 生成配置不会产生任何性能影响;在这种情况下,Lua 只在启动时运行以生成配置,并且不涉及 Lua 处理请求。
由于 lua_State
本身不是线程安全的,您有两种方式使用 Lua 配置
-
include_lua
和lua.plugin
:使用全局服务器锁,但在所有 worker 中共享同一个lua_State
-
lua_handler
:不带锁,每个 worker 都有自己的lua_State
(并且它们不能共享其全局上下文)。
Lua 配置
本节描述如何将主配置中的概念转换为 Lua。您可以将整个配置或部分配置用 Lua 编写,并将其包含进来(例如使用 include_lua
)。
示例 - debug.lua
以下保存为“debug.lua”的 Lua 片段可以例如通过 include_lua "debug.lua"
包含。
function mydebug(vr)
local url_fields = { "raw", "raw_path", "raw_orig_path", "scheme", "authority", "path", "query", "host" }
local phys_fields = { "path", "doc_root", "pathinfo" }
if vr:handle_direct() then
vr.resp.status = 200
vr.resp.headers["Content-Type"] = "text/plain"
vr.out:add("Hello World!\n\n")
vr.out:add("http method: " .. vr.req.http_method .. "\n")
vr.out:add("http version: " .. vr.req.http_version .. "\n")
for k, v in vr.env:pairs() do
vr.out:add("Env['" .. k .. "'] = '" .. v .. "'\n")
end
vr.out:add("\n")
for k, v in pairs(url_fields) do
vr.out:add("vr.req.uri['" .. v .. "'] = '" .. vr.req.uri[v] .. "'\n")
end
vr.out:add("\n")
for k, v in pairs(phys_fields) do
vr.out:add("vr.phys['" .. v .. "'] = '" .. vr.phys[v] .. "'\n")
end
vr.out:add("\n")
for k, v in vr.req.headers:pairs() do
vr.out:add("vr.req.headers['" .. k .. "'] = '" .. v .. "'\n")
end
end
end
actions = mydebug
值
- 布尔值:Lua 直接支持
true
和false
- 整数:Lua 有自己的数字类型(通常是
double
),并且不知道任何后缀。 - 字符串:Lua 直接支持字符串。请查阅 Lua 参考手册以了解各种引用样式。
- 列表和键值对列表:Lua 有一个“table”类型;它可以包含顺序列表和关联映射。使用
{1, 2, 3}
创建简单列表,使用{a=1, b=2}
创建唯一映射(将转换为键值对列表),或使用{{"a",1},{"a",2}}
显式创建键值对列表(其中一个键可以多次使用,并且顺序很重要)。
不要混合使用顺序列表和关联映射。
如果您从 lighttpd 获取一个列表(可能是键值对列表)值,它会表示为顺序列表,但具有一个特殊的__index
元表方法,支持字符串和nil
作为查找参数,即您可以在 Lua 中将键值对列表视为关联映射(例如参阅 contrib/secdownload.lua 中的选项处理)。 - 表达式和变量只是普通的 Lua 内容;目前(尚未)无法直接访问 lighttpd 配置变量。
- 动作块:您可以使用列表动作 (
act = action.list(act1, act2)
) 从动作列表中创建一个动作。
函数调用
动作上下文通过函数名前缀 action.
给出,设置上下文通过前缀 setup.
给出。不要尝试在请求处理中调用设置。
此外,每个 Lua 函数都可以作为动作(参阅上面的 debug.lua 示例),将一个虚拟请求对象作为参数。
不支持包含,也不支持调试 __print
(有其他日志方法可用)。
条件
条件是最糟糕的部分:无法将原生 Lua 的 if 语句转换为 lighttpd 配置,因此需要手动构建它们。
在 Lua 中,只有条件变量的长名称可用。所有条件运算符都已命名并附加到条件变量中,然后用作比较值来调用。
运算符 | Lua 名称 | 运算符 | Lua 名称 |
---|---|---|---|
== | :eq |
!= | :ne |
<= | :le |
< | :lt |
>= | :ge |
> | :gt |
=~ | :match |
!~ | :nomatch |
=^ | :prefix |
!^ | :notprefix |
=$ | :suffix |
!$ | :notsuffix |
=/ | :ip |
!/ | :notip |
布尔条件变量使用 :is()
或 :isnot()
调用。
此类调用(一个“条件”)的结果将作为第一个参数传递给 action.when
。
示例 - 仅限管理员
将 if req.env["REMOTE_USER"] != "admin" { auth.deny; }
转换为 Lua
actions = action.when(request.environment["REMOTE_USER"]:ne("admin"), action.auth.deny())
示例 - 仅限物理文件
将 if !phys.exists { auth.deny; }
转换为 Lua
actions = action.when(physical.exists:isnot(), action.auth.deny())
API
本节介绍了处理请求所需的对象类型;您可能会从处理程序中作为参数获得的虚拟请求对象开始。
对象字段应使用 .field
或 ["field"]
访问,例如
e = vr.env e["XXX"] = "abc"
标记为 (ro) 的字段是只读的;这并不意味着字段值不能被修改,只是您不能用另一个对象覆盖该字段。不过,只读字符串/数字属性确实是只读的。
使用 :method(...)
调用对象方法
vr:print("Hello World")
注意:obj:method(par1, par2, ...)
语法只是 obj["method"](obj, par1, par2, ...)
的另一种表达方式(但 obj
只会评估一次),因此字段名和方法名位于相同的命名空间中。
这意味着我们的容器类型无法访问与方法同名的字段(以“__”开头的方法未在此处列出),因此您必须使用显式访问方法来读取此类容器中的通用字段(写入不是问题,因为我们不允许写入方法)。
所有容器类型都应提供 get
和 set
方法,以提供“干净”的容器内容访问方式。
pairs()
某些对象可能会提供 :pairs()
方法来遍历字段(而非方法);这适用于简单的情况,例如
for k, v in vr.env:pairs() do vr:print("env['" .. k .. "'] = '" .. v .. "'") end
Lua 期望 :pairs
方法返回一个 next, obj, startkey
元组,并使用 k = startkey; while k, v = next(obj, k) do ... end
循环遍历列表;但 next()
方法应使用 k
作为上一个键并返回下一个键。
我们的 next
方法将在一个内部对象中(作为 next
函数的 upvalue 关联)保持当前位置,并且在每次调用时都会忽略 obj
和 k
参数并向前推进。
全局常量
liHandlerResult
枚举值
lighty.HANDLER_GO_ON
lighty.HANDLER_COMEBACK
lighty.HANDLER_WAIT_FOR_EVENT
lighty.HANDLER_ERROR
全局方法
-
lighty.print
(以及lighty.error
和print
):通过 Lua 的“tostring”方法在全局服务器上下文中以 ERROR 级别打印参数 -
lighty.warning
:通过 Lua 的“tostring”方法在全局服务器上下文中以 WARNING 级别打印参数 -
lighty.info
:通过 Lua 的“tostring”方法在全局服务器上下文中以 INFO 级别打印参数 -
lighty.debug
:通过 Lua 的“tostring”方法在全局服务器上下文中以 DEBUG 级别打印参数 -
lighty.filter_in(class)
:创建一个新动作,如果在运行时调用,它会从class:new(vr)
添加一个入站过滤器 -
lighty.filter_out(class)
:创建一个新动作,如果在运行时调用,它会从class:new(vr)
添加一个出站过滤器 -
lighty.md5(str)
:计算字符串str
的 md5 校验和(以十六进制字符串形式返回摘要) -
lighty.sha1(str)
:计算字符串str
的 sha1 校验和(以十六进制字符串形式返回摘要) -
lighty.sha256(str)
:计算字符串str
的 sha256 校验和(以十六进制字符串形式返回摘要) -
lighty.path_simplify(str)
:返回简化路径
示例
lighty.print("Hello World!")
示例
local MyFilterclass = { }
MyFilterClass.__index = MyFilterClass
function MyFilterClass:new(vr)
local o = { }
setmetatable(o, self)
return o -- return nil if you want to skip the filter this time
end
function MyFilterClass:handle(vr, outq, inq) ... end
actions = lighty.filter_out(MyFilterClass)
虚拟请求
字段
-
con
(ro):连接 -
in
(ro):数据块队列,读取请求的 post 内容 -
out
(ro):数据块队列,写入响应内容 -
env
(ro):环境,(fast)cgi 环境 -
req
(ro):请求,来自请求头的数据 -
resp
(ro):响应,响应头数据 -
phys
(ro):物理,路径和文件名 -
is_handled
(ro):vrequest 是否已处理 -
has_response
(ro):响应头(和状态)是否可用
方法
-
print(...)
:通过 Lua 的tostring
方法在虚拟请求上下文中以 ERROR 级别打印参数 -
warning(...)
:通过 Lua 的tostring
方法在虚拟请求上下文中以 WARNING 级别打印参数 -
info(...)
:通过 Lua 的tostring
方法在虚拟请求上下文中以 INFO 级别打印参数 -
debug(...)
:通过 Lua 的tostring
方法在虚拟请求上下文中以 DEBUG 级别打印参数 -
handle_direct()
:处理 vrequest(即提供头和正文);如果尚未处理,则返回 true。 -
enter_action(act)
:将新动作推送到动作堆栈(如果推送的动作完成后需要重新运行,则返回 HANDLER_WAIT_FOR_EVENT;如果完成,则返回 HANDLER_GO_ON) -
st, res, errno, msg = stat(filename)
:异步 stat(filename)。可能的结果如下- st 是 stat 结果,如果找到文件,则 res == HANDLER_GO_ON。errno 和 msg 为 NIL。在所有其他情况下,st 为 NIL 且 res != HANDLER_GO_ON。
- res == HANDLER_WAIT_FOR_EVENT:stat() 正在进行中,稍后再试(同时返回 HANDLER_WAIT_FOR_EVENT)
- res == HANDLER_ERROR:如果 stat() 失败,errno 包含错误号,msg 包含错误号对应的错误消息。
-
add_filter_in(obj)
:将obj
添加为 Lua 入站过滤器(需要响应obj:handle(vr, outq, inq)
和可选的obj:finished()
);返回一个 Filter 对象 -
add_filter_out(obj)
:将obj
添加为 Lua 出站过滤器(需要响应obj:handle(vr, outq, inq)
和可选的obj:finished()
);返回一个 Filter 对象
连接
-
local
:本地套接字地址 -
remote
:远程主机地址
环境
字段是环境中的键,因此它表现得像一个 Lua 表;如果您使用以“__”开头的键或与下面某个方法同名的键,则必须使用 get
方法来读取它们,例如
x = env["set"] -- doesn't work, returns the set method instead
x = env:get("set") -- use this instead
x = env[y] -- don't do this, as y may be a special key like "set"
x = env:get(y) -- just do it the safe way if you are not sure
方法
-
get(k)
:env[k]
的安全方式 -
set(k, v)
:env[k] = v
的安全方式 -
unset(k)
:env[k] = nil
的安全方式 -
weak_set(k, v)
:不覆盖旧值,env[k] = env[k] or v
的安全方式 -
pairs()
:用于遍历键:for k, v in env:pairs() do ... end
-
clear()
:删除所有条目
数据块队列
字段
-
is_closed
:ChunkQueue 是否已关闭
方法
-
add(s)
:将字符串附加到队列 -
add({filename="/..."})
:将文件附加到队列(只允许常规文件) -
reset()
:移除所有数据块,重置计数器 -
steal_all(from)
:从另一个队列窃取所有数据块(在过滤器中很有用,如果您决定将所有数据通过它传递) -
skip_all()
:跳过所有数据块(移除所有数据块,但不重置计数器)
请求
字段
-
headers
(ro):HTTP 头 -
http_method
(ro):HTTP 方法字符串(“GET”,“POST”,“HEAD”,…) -
http_version
(ro):HTTP 版本字符串(“HTTP/1.0”,“HTTP/1.1”) -
content_length
(ro):Content-Length 头的数值(如果有人更改了头值,则不会自动更新),未指定则为 -1 -
uri
:请求 URI
请求 URI
字段
-
raw
:HTTP 请求行中的原始请求 URI(或重写结果) -
raw_path
:未解码的带查询字符串的路径(对于大多数请求将与raw
相同,除非有人执行类似GET http://example.com/test?abc HTTP/1.1
的操作) -
raw_orig_path
:与 raw_path 相同,但在任何重写发生之前保存 -
scheme
:“http”或“https” -
authority
:完整的 hostname 头(或绝对 URL 中的 authority 部分),例如 “user@www.example.com.:8080” -
path
:已解码和简化的路径名,不含 authority、scheme、query-string;例如 “/index.php” -
host
:简单主机名,不含认证信息、端口、末尾点;例如 “www.example.com” -
query
:查询字符串,例如 “a=1&b=2”
响应
字段
-
headers
(ro):HTTP 头 -
status
:HTTP 状态码
物理
字段
-
path
:物理路径 -
doc_root
:文档根目录 -
pathinfo
:pathinfo
HTTP 头
字段与 Environment 具有相同的限制。
方法
-
get(k)
:将键k
的所有头值用 “, “ 连接起来(RFC 允许这样做) -
set(k, v)
:移除所有键为k
的头,如果 v 不为 nil,则附加新的 “k: v” 头 -
append(k, v)
:如果键 k 的最后一个头值已存在,则附加 “, v”;否则,使用insert(k, v)
-
insert(k, v)
:将新的 “k: v” 头附加到列表 -
unset(k)
:移除所有键为k
的头 -
pairs()
:遍历所有头。请注意,键不是唯一的! -
list(k)
:遍历所有键为k
的头 -
clear()
:移除所有头
过滤器
表示一个“liFilter”。
字段
-
in
(ro):数据块队列,入站流 -
out
(ro):数据块队列,出站流
Stat 结构体
表示“struct stat”。大多数字段应该是自解释的(如果您不了解,可以查阅 man 2 stat
(debian 手册页))。
字段
-
is_file
(ro):S_ISREG(mode) -
is_dir
(ro):S_ISDIR(mode) -
is_char
(ro):S_ISCHR(mode) -
is_block
(ro):S_ISBLK(mode) -
is_socket
(ro):S_ISSOCK(mode) -
is_link
(ro):S_ISLNK(mode) -
is_fifo
(ro):S_ISFIFO(mode) -
mode
(ro) -
mtime
(ro) -
ctime
(ro) -
atime
(ro) -
uid
(ro) -
gid
(ro) -
size
(ro) -
ino
(ro) -
dev
(ro)