关于OpenResty里的ngx.on_abort

关于 OpenResty 里的 ngx.on_abort,官方文档里是这样说明的:

Registers a user Lua function as the callback which gets called automatically when the client closes the (downstream) connection prematurely.

也就是说:当客户端提前关闭连接的时候,在 ngx.on_abort 里注册的函数会被触发,下面做个实验看看,把如下代码加入 nginx.conf,并 reload 进程使其生效:

lua_check_client_abort on;

location /test_http {
    content_by_lua_block {
        ngx.on_abort(function(a)
            ngx.log(ngx.ERR, "abort")
        end)

        ngx.sleep(100)
    }
}

开启两个命令行窗口:一个 tail -f error.log,另一个 curl http://127.0.0.1/test_http。因为我在代码里设置了 sleep,所以 curl 无疑会卡住,通过执行 Ctrl+c 强行终止,以此构造出一个客户端提前关闭连接的场景,此时我们在 tail 窗口就能看到输出了 log,由此可知当客户端提前关闭连接的时候,在 http 的情况下, ngx.on_abort 里注册的函数会被触发。

不过如果你仔细查看和 ngx.on_abort 相关的 lua_check_client_abort 的文档,会发现:

According to the current implementation, however, if the client closes the connection before the Lua code finishes reading the request body data via ngx.req.socket, then ngx_lua will neither stop all the running “light threads” nor call the user callback (if ngx.on_abort has been called). Instead, the reading operation on ngx.req.socket will just return the error message “client aborted” as the second return value (the first return value is surely nil).

也就是说,当客户端提前关闭连接的时候,如果 ngx.req.socket 中的数据没有被读取,那么 ngx.on_abort 里注册的函数不会被触发。实际上指的就是非 http 情况,也就是自己处理 socket 的情况,比如 websocket:

lua_check_client_abort on;

location /test_websocket {
    content_by_lua_block {
        ngx.on_abort(function()
            ngx.log(ngx.ERR, "abort")
        end)

        local server = require "resty.websocket.server"
        local wb = server:new()

        while true do
            local data, typ, err = wb:recv_frame()

            if wb.fatal then
                ngx.log(ngx.ERR, err)
                return ngx.exit(444)
            end
        end
    }
}

然后通过一个网页来调用如上的 websocket 服务:

<script>
var ws = new WebSocket('ws://127.0.0.1/websocket');
ws.send("test");
</script>

与之前不同的是,因为我们要执行网页中的 javascript 代码,所以必须通过浏览器来执行 test_websocket 请求,而不是 curl,当请求发出后,会连接 websocket 发送一个字符串,直接关闭浏览器,以此构造出一个客户端提前关闭连接的场景,需要说明的是这里不用 sleep,因为已经连上 websocket 了,此时我们在 tail 窗口就能看到输出了 log,但是却不是 ngx.on_abort 的 log,而是 wb.fatal 的 log:

failed to receive the first 2 bytes: client aborted

由此可知当客户端提前关闭连接的时候,在 websocket 的情况下, ngx.on_abort 里注册的函数不会被触发。

结论:通过 ngx.on_abort 检测客户端是否提前断开连接的方法,仅仅对 http 场景有效,对其它需要要手动处理 socket 数据的场景(比如 websocket)无效,对于此类场景,可以读取 socket 数据,通过报错信息来判断客户端是否提前关闭了连接,需要留意的是文档描述是错误信息等于「client aborted」表示客户端提前关闭了连接,但是 lua-resty-websocket 把这个报错信息重新加工了一下,所以不能按等于判断,必须按包含判断,可见通过判断错误信息等于什么来判断错误类型的方式有点不靠谱,其实在 websocket 场景,如果要求不是特别严格的话,那么只要 fatal 为真就可以认为客户端已经断开了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注