应该什么时候http.RoundTripper关闭它的连接?

时间:2017-10-19 16:31:20

标签: http go

我正在使用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连接关闭。

1 个答案:

答案 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频道,您可以使用现有的Transportnet.Dialer进行ssh连接,并将频道封装在net.Conn中接口。