200 lines
5.5 KiB
Go
200 lines
5.5 KiB
Go
// Package lua 提供 ngx.resp API 实现。
|
||
//
|
||
// 该文件实现 nginx 风格的响应操作 Lua API,用于操作 HTTP 响应。
|
||
// 兼容 OpenResty/ngx_lua 的 ngx.resp 语义。
|
||
//
|
||
// 主要功能:
|
||
// - ngx.resp.get_status():获取响应状态码
|
||
// - ngx.resp.set_status(code):设置响应状态码
|
||
// - ngx.resp.get_headers():获取响应头表
|
||
// - ngx.resp.set_header(key, value):设置响应头
|
||
// - ngx.resp.clear_header(key):清除响应头
|
||
//
|
||
// 注意事项:
|
||
// - 响应头缓存使用 sync.Once 延迟初始化
|
||
// - 修改响应头后会自动清除缓存
|
||
// - Scheduler 模式下 ngx.resp 不可用
|
||
//
|
||
// 作者:xfy
|
||
package lua
|
||
|
||
import (
|
||
"sync"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
glua "github.com/yuin/gopher-lua"
|
||
)
|
||
|
||
// ngxRespAPI ngx.resp API 实现
|
||
type ngxRespAPI struct {
|
||
// 请求上下文(包含 fasthttp.Response)
|
||
ctx *fasthttp.RequestCtx
|
||
|
||
// 缓存:响应头表
|
||
headersCache map[string][]string
|
||
headersCacheOnce sync.Once
|
||
}
|
||
|
||
// newNgxRespAPI 创建 ngx.resp API 实例
|
||
func newNgxRespAPI(ctx *fasthttp.RequestCtx) *ngxRespAPI {
|
||
return &ngxRespAPI{
|
||
ctx: ctx,
|
||
headersCache: nil, // 延迟初始化
|
||
}
|
||
}
|
||
|
||
// RegisterNgxRespAPI 在 Lua 状态机中注册 ngx.resp API
|
||
// 这是主入口函数,由 LuaEngine 在初始化时调用
|
||
func RegisterNgxRespAPI(L *glua.LState, api *ngxRespAPI) {
|
||
// 获取或创建 ngx 表
|
||
ngxTable := GetOrCreateNgxTable(L)
|
||
|
||
// 获取或创建 ngx.resp 子表
|
||
ngxResp := GetOrCreateNgxSubTable(ngxTable, L, "resp")
|
||
|
||
// 每次请求更新函数以绑定正确的 ctx
|
||
ngxResp.RawSetString("get_status", L.NewFunction(api.luaGetStatus))
|
||
ngxResp.RawSetString("set_status", L.NewFunction(api.luaSetStatus))
|
||
ngxResp.RawSetString("get_headers", L.NewFunction(api.luaGetHeaders))
|
||
ngxResp.RawSetString("set_header", L.NewFunction(api.luaSetHeader))
|
||
ngxResp.RawSetString("clear_header", L.NewFunction(api.luaClearHeader))
|
||
}
|
||
|
||
// ==================== API 实现 ====================
|
||
|
||
// luaGetStatus 实现 ngx.resp.get_status()
|
||
// Lua 调用: local status = ngx.resp.get_status()
|
||
// 返回: number (HTTP 状态码,如 200, 404, 500 等)
|
||
func (api *ngxRespAPI) luaGetStatus(L *glua.LState) int {
|
||
status := api.ctx.Response.StatusCode()
|
||
L.Push(glua.LNumber(status))
|
||
return 1
|
||
}
|
||
|
||
// luaSetStatus 实现 ngx.resp.set_status(code)
|
||
// Lua 调用: ngx.resp.set_status(404)
|
||
// 参数: code (number) - HTTP 状态码
|
||
// 返回: 无
|
||
func (api *ngxRespAPI) luaSetStatus(L *glua.LState) int {
|
||
code := L.CheckInt(1)
|
||
api.ctx.Response.SetStatusCode(code)
|
||
return 0
|
||
}
|
||
|
||
// luaGetHeaders 实现 ngx.resp.get_headers(max_headers?)
|
||
// Lua 调用: local headers = ngx.resp.get_headers() 或 ngx.resp.get_headers(100)
|
||
// 参数: max_headers (number, 可选) - 最大返回头数量,默认为 100
|
||
// 返回: table (响应头表,如 { ["Content-Type"] = "text/html", ... })
|
||
func (api *ngxRespAPI) luaGetHeaders(L *glua.LState) int {
|
||
// 获取可选的 max_headers 参数
|
||
maxHeaders := 100
|
||
if L.GetTop() >= 1 {
|
||
maxHeaders = L.CheckInt(1)
|
||
if maxHeaders <= 0 {
|
||
maxHeaders = 100
|
||
}
|
||
}
|
||
|
||
// 延迟初始化缓存
|
||
api.headersCacheOnce.Do(func() {
|
||
api.headersCache = api.parseHeaders()
|
||
})
|
||
|
||
// 构建 Lua 表
|
||
result := L.NewTable()
|
||
count := 0
|
||
|
||
for key, values := range api.headersCache {
|
||
if count >= maxHeaders {
|
||
break
|
||
}
|
||
|
||
if len(values) == 1 {
|
||
// 单值:直接存储为字符串
|
||
result.RawSetString(key, glua.LString(values[0]))
|
||
} else if len(values) > 1 {
|
||
// 多值:存储为数组(table)
|
||
arr := L.NewTable()
|
||
for i, v := range values {
|
||
arr.RawSetInt(i+1, glua.LString(v)) // Lua 数组从 1 开始
|
||
}
|
||
result.RawSetString(key, arr)
|
||
}
|
||
count++
|
||
}
|
||
|
||
L.Push(result)
|
||
return 1
|
||
}
|
||
|
||
// luaSetHeader 实现 ngx.resp.set_header(key, value)
|
||
// Lua 调用: ngx.resp.set_header("Content-Type", "application/json")
|
||
// 参数:
|
||
// - key (string) - 头名称
|
||
// - value (string) - 头值
|
||
//
|
||
// 返回: 无
|
||
func (api *ngxRespAPI) luaSetHeader(L *glua.LState) int {
|
||
key := L.CheckString(1)
|
||
value := L.CheckString(2)
|
||
|
||
api.ctx.Response.Header.Set(key, value)
|
||
|
||
// 清除缓存,下次 get_headers 会重新解析
|
||
api.headersCache = nil
|
||
api.headersCacheOnce = sync.Once{}
|
||
|
||
return 0
|
||
}
|
||
|
||
// luaClearHeader 实现 ngx.resp.clear_header(key)
|
||
// Lua 调用: ngx.resp.clear_header("X-Custom-Header")
|
||
// 参数: key (string) - 要清除的头名称
|
||
// 返回: 无
|
||
func (api *ngxRespAPI) luaClearHeader(L *glua.LState) int {
|
||
key := L.CheckString(1)
|
||
|
||
api.ctx.Response.Header.Del(key)
|
||
|
||
// 清除缓存,下次 get_headers 会重新解析
|
||
api.headersCache = nil
|
||
api.headersCacheOnce = sync.Once{}
|
||
|
||
return 0
|
||
}
|
||
|
||
// ==================== 辅助函数 ====================
|
||
|
||
// parseHeaders 解析响应头为 map
|
||
func (api *ngxRespAPI) parseHeaders() map[string][]string {
|
||
result := make(map[string][]string)
|
||
|
||
// 遍历所有响应头(使用 All 替代已弃用的 VisitAll)
|
||
for key, value := range api.ctx.Response.Header.All() {
|
||
keyStr := string(key)
|
||
valueStr := string(value)
|
||
|
||
if existing, ok := result[keyStr]; ok {
|
||
result[keyStr] = append(existing, valueStr)
|
||
} else {
|
||
result[keyStr] = []string{valueStr}
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// GetHeader 获取单个响应头值(辅助函数,供外部调用)
|
||
func (api *ngxRespAPI) GetHeader(name string) string {
|
||
return string(api.ctx.Response.Header.Peek(name))
|
||
}
|
||
|
||
// SetHeader 设置单个响应头(辅助函数,供外部调用)
|
||
func (api *ngxRespAPI) SetHeader(name, value string) {
|
||
api.ctx.Response.Header.Set(name, value)
|
||
|
||
// 清除缓存
|
||
api.headersCache = nil
|
||
api.headersCacheOnce = sync.Once{}
|
||
}
|