You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Go实现按SNI选择转发或终止TLS连接的服务?

解决方案

方案1:使用Go 1.21+原生公开的ServeConnAPI

你要的HandleConnection能力其实标准库已经提供,Go 1.21正式导出了http.Server.ServeConn方法,专门用于将单条连接交给HTTP服务器处理,完全匹配你的需求。
你只需要把peekClientHello返回的带缓冲区的reader包装成net.Conn,直接调用该方法即可:

// 实现带缓冲区的Conn,保证已经读取的ClientHello字节能被后续TLS握手读到
type bufferedConn struct {
    net.Conn
    r io.Reader
}
func (bc bufferedConn) Read(p []byte) (int, error) {
    return bc.r.Read(p)
}

你的TODO部分可以直接替换为:

// 注意:要把开头的defer clientConn.Close()移到转发分支,这里连接交给HTTP服务器管理,不需要手动关闭
server.ServeConn(bufferedConn{
    Conn: clientConn,
    r: clientReader,
}, http.ServeConnOpts{
    TLSConfig: &server.TLSConfig, // 传入你的TLS证书配置即可,自动完成TLS握手
})
return

该方案最简洁,所有标准库HTTP服务器的原生能力(超时、连接池、优雅关闭等)都可以正常使用。


方案2:兼容旧版本Go的合成Listener实现

你之前想到的合成Listener方案是工业界非常成熟的常用实现,一点都不冗余,代码实现非常轻量:

type ChanListener struct {
    connChan chan net.Conn
    closed   chan struct{}
}

func NewChanListener() *ChanListener {
    return &ChanListener{
        connChan: make(chan net.Conn),
        closed:   make(chan struct{}),
    }
}

func (l *ChanListener) Accept() (net.Conn, error) {
    select {
    case conn := <-l.connChan:
        return conn, nil
    case <-l.closed:
        return nil, errors.New("listener closed")
    }
}

func (l *ChanListener) Close() error {
    close(l.closed)
    return nil
}

func (l *ChanListener) Addr() net.Addr {
    return &net.TCPAddr{IP: net.IPv4zero, Port: 0}
}

// 自定义方法:将需要本地处理的连接喂给HTTP服务器
func (l *ChanListener) FeedConn(c net.Conn) {
    select {
    case l.connChan <- c:
    case <-l.closed:
        c.Close()
    }
}

改造你的main函数,单独启动goroutine跑HTTP服务:

func main() {
    l, _ := net.Listen("tcp", ":443")
    chanListener := NewChanListener()
    server := http.Server{
        TLSConfig: &tls.Config{
            // 填入你的证书配置
        },
    }
    // 后台启动HTTP服务
    go func() {
        log.Fatal(server.ServeTLS(chanListener, "", ""))
    }()

    for {
        conn, err := l.Accept()
        if err != nil {
            continue
        }
        go handleConnection(conn, chanListener)
    }
}

TODO部分替换为:

// 同样需要先包装bufferedConn
chanListener.FeedConn(bufferedConn{Conn: clientConn, r: clientReader})
return

该方案兼容所有Go版本,完全解耦SNI路由逻辑和HTTP服务逻辑,适合需要兼容旧版本的场景。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 17:24:05