我正在使用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(因为去了ssh.DiscardRequests(req)),所以我在每个HTTP请求上泄漏goroutine,直到底层的SSH连接关闭。
答案 0 :(得分:1)
http.RoundTripper
在响应主体完全耗尽之后或服务器请求之前不应关闭连接。
最简单的选择是完全缓冲响应并立即关闭连接。在某些情况下,如果流量主要由小的独立请求组成,那么这实际上可能是最有效的。
下一个选项是挂钩关闭响应主体以关闭通道。
type Body struct {
io.ReadCloser
channel ssh.Channel
}
func (b *Body) Close() error {
b.channel.Close()
return b.ReadCloser.Close()
}
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())
}
err = req.Write(ch)
if err != nil {
return nil, errors.New("couldn't send request: " + err.Error())
}
resp, err := http.ReadResponse(bufio.NewReader(ch), req)
if err != nil {
ch.Close()
return nil, err
}
resp.Body = &Body{
ReadCloser: resp.Body,
channel: ch,
}
return resp, err
}
最后,为了最有效地使用ssh频道,您可以使用现有的Transport
与net.Dialer
进行ssh连接,并将频道封装在net.Conn
中接口。