OpenResty单端口多协议支持研究

需求背景
443端口需要提供2种协议。HTTPS和一种额外的私有协议。私有协议还需要通过sendmsg将fd发送给专门处理私有协议的服务。

方案1
一开始,想在openresty的balancer模块中实现。但发现不行,因为balancer的上下文中没办法预计一部分数据。
有人提了issue,官方还没回应。https://github.com/openresty/lua-nginx-module/issues/2330

方案2
利用preread_by_lua_* gateway能力,它可以通过cosocket:peek预读一部分tcp数据。

还要可以获取到底层连接的FD。
社区有人提了PR,https://github.com/openresty/lua-nginx-module/pull/1147/files。但官方回复说这会破坏底层的IO封装,建议通过ngx.semaphore + lua_shared_dict + ngx.sleep来实现。

一开始没加思考完全照抄了其中推荐的ffi函数 ngx_http_lua_ffi_socket_tcp_sslhandshake。但测的时候发现获取的fd并不对。不知道原因。
调试了很久,反应过来了:不应该访问http的接口,因为此时连接刚建立,处于stream的上下文中,fd.c中按照http请求的结构体定义不对,应该按照stream的。
找了半天没找到ngx 头文件中的对应结构体,后来发现openresty项目 https://github.com/openresty/stream-lua-nginx-module/blob/master/src/ngx_stream_lua_request.h#L22中有定义。直接修改下,就拿到正常的fd。

local ngx = require 'ngx'
local base = require "resty.core.base"
base.allows_subsystem('http', 'stream')
local r = base.get_request()
local ffi = require 'ffi'
local C = ffi.C
local lib = ffi.load('/tmp/fd.so')

ffi.cdef[[
long ngx_http_lua_ffi_socket_fd(void *r);
int lua_ffi_socket_fd(void *r);
]]

-- local sock = ngx.req.socket()
-- local tcp_sock = sock[1]
-- local fd = C.ngx_http_lua_ffi_socket_fd(r)
local fd = lib.lua_ffi_socket_fd(r)
-- ngx.log(1, "hi", tcp_sock)
ngx.log(1, string.format("fd is: %d", fd))

fd.so的源码
fd.c

#include "header.h"
#include <stddef.h>
int lua_ffi_socket_fd(ngx_http_request_t *r)
{
if (r == NULL)
return -1;
return r->connection->fd;
}

header.h
typedef struct {
void* data;
void* data1;
void* data2;
int fd;
} ngx_connection_t;
typedef struct {
ngx_connection_t *connection;
} ngx_http_request_t;

这样是为了不修改openresty源码,只要结构体的成员偏移量与openresty中保持一致,即可获取到正确的fd。

最终完整的方案如下。剩下的就是在test.lua中,识别是私有协议,把连接转走即可。具体可以参考社区的好心人提供的示例:https://github.com/gustavosbarreto/websocket-fd-handoff

stream {
    server {
        listen 1080;
        proxy_pass 127.0.0.1:80;
        preread_by_lua_file test.lua;
    }
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;
...
}
posted @ 2025-06-13 09:57  lin2learn  阅读(33)  评论(0)    收藏  举报