【问题标题】:When should an http.RoundTripper close its connection?http.RoundTripper 什么时候应该关闭它的连接?
【发布时间】: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 代理一样。

标签: http go


【解决方案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 接口中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-08
    • 2012-02-28
    • 2022-01-02
    • 1970-01-01
    • 2013-12-27
    • 1970-01-01
    • 2017-09-11
    相关资源
    最近更新 更多