Replace use of httputil in client hijack

Simplify the hijack process by just performing the http request/response
on the connection and returning the raw conn after success. The client
conn from httputil is deprecated and easily replaced.

Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
Derek McGowan 2023-12-09 22:10:03 -08:00
parent 74cf9a0f60
commit bb4e9c72d7
No known key found for this signature in database
GPG key ID: F58C5D0A4405ACDB

View file

@ -6,7 +6,6 @@ import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
@ -81,6 +80,11 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn,
if err != nil {
return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
}
defer func() {
if retErr != nil {
conn.Close()
}
}()
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
@ -92,11 +96,10 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn,
_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
clientconn := httputil.NewClientConn(conn, nil)
defer clientconn.Close()
hc := &hijackedConn{conn, bufio.NewReader(conn)}
// Server hijacks the connection, error 'connection closed' expected
resp, err := clientconn.Do(req)
resp, err := hc.RoundTrip(req)
if resp != nil {
// This is a simplified variant of "httpconv.ClientStatus(resp.StatusCode))";
//
@ -121,29 +124,25 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn,
span.SetStatus(code, "")
}
//nolint:staticcheck // ignore SA1019 for connecting to old (pre go1.8) daemons
if err != httputil.ErrPersistEOF {
if err != nil {
return nil, "", err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
_ = resp.Body.Close()
return nil, "", fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
}
if err != nil {
return nil, "", err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
_ = resp.Body.Close()
return nil, "", fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
}
c, br := clientconn.Hijack()
if br.Buffered() > 0 {
if hc.r.Buffered() > 0 {
// If there is buffered content, wrap the connection. We return an
// object that implements CloseWrite if the underlying connection
// implements it.
if _, ok := c.(types.CloseWriter); ok {
c = &hijackedConnCloseWriter{&hijackedConn{c, br}}
if _, ok := hc.Conn.(types.CloseWriter); ok {
conn = &hijackedConnCloseWriter{hc}
} else {
c = &hijackedConn{c, br}
conn = hc
}
} else {
br.Reset(nil)
hc.r.Reset(nil)
}
var mediaType string
@ -152,7 +151,7 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn,
mediaType = resp.Header.Get("Content-Type")
}
return c, mediaType, nil
return conn, mediaType, nil
}
// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case
@ -164,6 +163,13 @@ type hijackedConn struct {
r *bufio.Reader
}
func (c *hijackedConn) RoundTrip(req *http.Request) (*http.Response, error) {
if err := req.Write(c.Conn); err != nil {
return nil, err
}
return http.ReadResponse(c.r, req)
}
func (c *hijackedConn) Read(b []byte) (int, error) {
return c.r.Read(b)
}