2017-10-19 57 views
1

我使用httputil.ReverseProxy與我自己實現的http.RoundTripper,它使用ssh.Channel作爲傳輸。我的RoundTrip方法大致如下所示:http.RoundTripper何時應關閉其連接?

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

我沒有想到這一點,但Response.Body只是一個io.ReadCloser。也許你可以包裝默認的Close()方法。然後你就會知道調用者何時完成響應,並且你可以關閉連接。 – Peter

+0

是的,這取決於客戶端使用和關閉'Response.Body',但關閉它並不直接關閉連接,它只會將它從主體中釋放。我認爲如果你想在這一層處理連接,而不是在傳輸層,你需要緩存整個響應並在RoundTrip中關閉連接。是否有一個原因,你不是簡單地使用SSH作爲網絡傳輸? – JimB

+0

@Peter好主意;實施和工作。也許值得回答? –

回答

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接口的通道。