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