http - 如何在 Go 中为来自同一个监听器的 SSH 和 HTTP(S) 流量提供服务?

标签 http go ssh

我希望能够在同一个端口上同时支持 SSH 和 HTTPS 连接。为此,我需要检查客户端发送的前几个字节,如果它以“SSH”开头,则以一种方式提供连接,但如果不是 SSH,则让 Go HTTP 服务器处理它。

但是 http 包只能与 net.Listener 一起使用。一旦我接受了来自监听器的连接并检查了第一个字节,就无法将 net.Conn 发送到 http。

我怎样才能做到这一点?

最佳答案

创建两个自定义监听器,一个用于 SSH 连接,一个用于所有其他连接。然后接受来自原始监听器的连接,查看第一个字节,并将连接发送到适当的监听器。

l := net.Listen("tcp", ":443")
sshListener, httpListener := MuxListener(l)
go sshServer.Serve(sshListener)
go httpServer.Serve(httpListener)

多路监听器:

// MuxListener takes a net.Listener and returns two listeners, one that
// accepts connections that start with "SSH", and another that accepts
// all others. This allows SSH and HTTPS to be served from the same port.
func MuxListener(l net.Listener) (ssh net.Listener, other net.Listener) {
    sshListener, otherListener := newListener(l), newListener(l)
    go func() {
        for {
            conn, err := l.Accept()
            if err != nil {
                log.Println("Error accepting conn:", err)
                continue
            }
            conn.SetReadDeadline(time.Now().Add(time.Second * 10))
            bconn := bufferedConn{conn, bufio.NewReaderSize(conn, 3)}
            p, err := bconn.Peek(3)
            conn.SetReadDeadline(time.Time{})
            if err != nil {
                log.Println("Error peeking into conn:", err)
                continue
            }
            prefix := string(p)
            selectedListener := otherListener
            if prefix == "SSH" {
                selectedListener = sshListener
            }
            if selectedListener.accept != nil {
                selectedListener.accept <- bconn
            }
        }
    }()
    return sshListener, otherListener
}

听众:

type listener struct {
    accept chan net.Conn
    net.Listener
}

func newListener(l net.Listener) *listener {
    return &listener{
        make(chan net.Conn),
        l,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    if l.accept == nil {
        return nil, errors.New("Listener closed")
    }
    return <-l.accept, nil
}

func (l *listener) Close() error {
    close(l.accept)
    l.accept = nil
    return nil
}

缓冲连接:

type bufferedConn struct {
    net.Conn
    r *bufio.Reader
}

func (b bufferedConn) Peek(n int) ([]byte, error) {
    return b.r.Peek(n)
}

func (b bufferedConn) Read(p []byte) (int, error) {
    return b.r.Read(p)
}

关于http - 如何在 Go 中为来自同一个监听器的 SSH 和 HTTP(S) 流量提供服务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47699392/

相关文章:

go - 使用 gRPC 的上下文与 SendMsg/RecvMsg 方法发送元数据

Go viper .yaml values 环境变量覆盖

linux - ssh 命令和引号

amazon-web-services - 如何通过 SSH 连接到 Redhat EC2 实例?

go - 当填充 channel 的函数调用未嵌入 Goroutine 中时,为什么会出现死锁?

java - VLCJ 无法在客户端使用 http 打开音频流

javascript - 在 Node.js 中发出 HTTP 请求并发送数据体

asp.net - 大型 Web 应用程序中的 HTTP Keep Alive

windows - 在 windows ssh 中连接到远程 zope 控制台

http - 如何设置nginx仅在修改时传输静态文件