You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

OpenResty Nginx需求:读取响应体后修改HTTP状态码

解决思路与配置示例

我刚好碰到过类似的场景,body_filter_by_lua_block(你可能笔误写成body_by_filter_lua_block了)确实没办法直接修改HTTP状态码。不过咱们可以利用Nginx的请求处理阶段配合上下文变量来实现需求——核心思路是在body过滤阶段检测响应体里的超时标记,再在header过滤阶段修改状态码,因为header阶段是发送响应头之前的最后时机,修改状态码在这里是有效的。

下面是完整的可运行配置片段:

http {
    lua_need_request_body on;

    server {
        listen 8000;

        location / {
            proxy_pass http://localhost:9200;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            # 初始化上下文变量,用来标记是否检测到超时
            header_filter_by_lua_block {
                ngx.ctx.timedout_detected = false
                ngx.ctx.original_status = ngx.status  # 可选:暂存原始状态码,方便后续恢复
            }

            # 分块处理响应体,检查是否包含超时字段
            body_filter_by_lua_block {
                local chunk = ngx.arg[1]
                if chunk then
                    # 用字符串匹配快速检测,适合大多数场景
                    if string.find(chunk, '"timedout":%s*true') then
                        ngx.ctx.timedout_detected = true
                    end
                end

                # 可选:如果需要精确解析JSON(避免误匹配),等响应体接收完成后解析
                if ngx.arg[2] then  # ngx.arg[2]为true表示响应体传输结束
                    ngx.ctx.full_body = (ngx.ctx.full_body or "") .. chunk
                    local cjson = require "cjson"
                    local ok, res = pcall(cjson.decode, ngx.ctx.full_body)
                    if ok and res.timedout == true then
                        ngx.ctx.timedout_detected = true
                    end
                end
            }

            # 发送响应头前,根据标记修改状态码
            header_filter_by_lua_block {
                if ngx.ctx.timedout_detected then
                    ngx.status = 504
                    # 加个自定义响应头,方便Grafana端排查问题
                    ngx.header["X-ES-Timeout"] = "Elasticsearch query timed out"
                end
            }
        }
    }
}

关键细节说明:

  • 阶段配合逻辑:Nginx会先执行header_filter初始化变量,再分块处理响应体(每块执行body_filter),最后在发送响应头给客户端前,通过header_filter读取标记修改状态码,这个时机修改状态码是完全有效的。
  • ngx.ctx的作用:这是请求级别的上下文容器,用来在不同Lua块之间传递状态,整个请求生命周期内都有效,完美解决跨阶段传值的问题。
  • 两种检测方式
    1. 字符串匹配:速度快,适合大多数场景,只要响应体里出现"timedout": true(中间允许任意空格)就会触发标记。
    2. JSON解析:更精确,能避免误匹配类似"timedout": trueabc的异常情况,但需要等整个响应体接收完成,适合响应体体积不大的场景。

注意事项:

  • 如果Elasticsearch返回的是超大响应体,拼接完整响应体可能占用较多内存,这时候优先用字符串匹配的方式,不用等全部内容接收完。
  • 确保你的OpenResty环境已经包含cjson模块(默认是自带的,如果缺失可以通过opm install openresty/lua-cjson安装)。

内容的提问来源于stack exchange,提问作者athavan kanapuli

火山引擎 最新活动