【发布时间】:2017-10-21 02:13:13
【问题描述】:
我正在使用httputil.ReverseProxy 和我自己的实现的http.RoundTripper,它使用ssh.Channel 作为传输。我的 RoundTrip 方法大致如下:
func (c SSHConnection) RoundTrip(req *http.Request) (*http.Response, error) {
ch, err := c.GetChannel()
if err != nil {
return nil, errors.New("couldn't open forwarded-tcpip channel: " + err.Error())
}
// defer ch.Close()
err = req.Write(ch)
if err != nil {
return nil, errors.New("couldn't send request: " + err.Error())
}
return http.ReadResponse(bufio.NewReader(ch), req)
}
func (c SSHConnection) GetChannel() (ssh.Channel, error) {
ch, req, err := c.Conn.OpenChannel("forwarded-tcpip", msg)
if err != nil {
return nil, err
}
go ssh.DiscardRequests(req)
return ch, nil
}
注意注释掉的延迟 ch.Close()。最初我天真地关闭了这里的连接,但响应正文有时会是空的,因为 HTTP 代理读取正文和关闭 SSH 通道之间存在竞争。
现在假设我不关心保持活动状态,我什么时候可以关闭 ssh.Channel?如果我不这样做,每个请求都会启动一个新的 goroutine(因为 go ssh.DiscardRequests(req)),所以我会在每个 HTTP 请求上泄漏一个 goroutine,直到底层 SSH 连接关闭。
【问题讨论】:
-
我还没想过这个,但是 Response.Body 只是一个 io.ReadCloser。也许您可以包装默认的 Close() 方法。然后你会知道调用者何时完成响应,你可以关闭连接。
-
是的,由客户端来消费和关闭
Response.Body,但是关闭它并不会直接关闭连接,它只会从主体中释放它。我认为如果您想在这一层而不是在传输层处理连接,您将需要缓冲整个响应并在 RoundTrip 中关闭您的连接。您是否有理由不将 ssh 仅用作网络传输? -
@Peter 好主意;实施和工作。也许值得回答?
-
@JimB 你介意再解释一下吗?我不确定您所说的“传输层”或“仅将 ssh 用作网络传输”是什么意思。
-
@TrevorDixon:“RoundTripper”(几乎总是
Transport)旨在处理RoundTrip之外的连接生命周期。连接在主体被消耗后的某个时间点关闭,通常由服务器启动,并且可能在RoundTrip返回后很长时间。 http 请求(又名“RoundTrip”)和连接处理发生在不同的概念层。 ssh 连接充当网络代理,因此要正常工作,它需要挂接到该级别,就像使用 http 或 socks 代理一样。