lolly/internal/lua/api_resp.go

200 lines
5.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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{}
}