Lua API

Lua 可以用于生成配置(类似于 include_shell 的快捷方式)或编写实际的响应处理程序。

使用 Lua 生成配置不会产生任何性能影响;在这种情况下,Lua 只在启动时运行以生成配置,并且不涉及 Lua 处理请求。

由于 lua_State 本身不是线程安全的,您有两种方式使用 Lua 配置

  • include_lualua.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 直接支持 truefalse
  • 整数: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 只会评估一次),因此字段名和方法名位于相同的命名空间中。
这意味着我们的容器类型无法访问与方法同名的字段(以“__”开头的方法未在此处列出),因此您必须使用显式访问方法来读取此类容器中的通用字段(写入不是问题,因为我们不允许写入方法)。
所有容器类型都应提供 getset 方法,以提供“干净”的容器内容访问方式。

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 关联)保持当前位置,并且在每次调用时都会忽略 objk 参数并向前推进。

全局常量

liHandlerResult 枚举值

  • lighty.HANDLER_GO_ON
  • lighty.HANDLER_COMEBACK
  • lighty.HANDLER_WAIT_FOR_EVENT
  • lighty.HANDLER_ERROR

全局方法

  • lighty.print(以及 lighty.errorprint):通过 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)