ServeHTTP hijacks the underlying connection of a http request. The underlying connection can then be reused to send any data, i.e., it gives a universal duplex connection to the client. Important notes to keep this stable: - The hijacked `net.Conn` is only guaranteed to be valid while the request ha
(w http.ResponseWriter, r *http.Request)
| 113 | // https://github.com/moby/moby/blob/e2ead4526d9416bd90502c710751839e55d739f2/daemon/server/router/container/exec.go#L118 |
| 114 | // https://cs.opensource.google/go/go/+/master:src/net/http/httputil/reverseproxy.go;l=750;drc=4ab1aec00799f91e96182cbbffd1de405cd52e93 |
| 115 | func (h *hijackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 116 | // Important: Do not write anything (e.g. headers) before the hijack! |
| 117 | hj, ok := w.(http.Hijacker) |
| 118 | if !ok { |
| 119 | http.Error(w, "hijacking connection", http.StatusInternalServerError) |
| 120 | return |
| 121 | } |
| 122 | |
| 123 | conn, brw, hijackErr := hj.Hijack() |
| 124 | if errors.Is(hijackErr, http.ErrNotSupported) { |
| 125 | h.logger.Errorf("can't switch protocols using non-Hijacker ResponseWriter") |
| 126 | return |
| 127 | } |
| 128 | |
| 129 | if hijackErr != nil { |
| 130 | h.logger.Errorf("Hijack failed on protocol switch: %v", hijackErr) |
| 131 | return |
| 132 | } |
| 133 | |
| 134 | defer func() { _ = conn.Close() }() // Might have already been called inside the callback -> ignore double close error |
| 135 | |
| 136 | done := make(chan struct{}) |
| 137 | go func() { |
| 138 | defer close(done) |
| 139 | |
| 140 | resp := &http.Response{ |
| 141 | StatusCode: http.StatusSwitchingProtocols, |
| 142 | Proto: r.Proto, |
| 143 | Header: w.Header(), |
| 144 | } |
| 145 | resp.Header.Set("Connection", "upgrade") |
| 146 | resp.Header.Set("Upgrade", "tcp") |
| 147 | if err := resp.Write(brw); err != nil { |
| 148 | h.logger.Errorf("writing response: %v", err) |
| 149 | return |
| 150 | } |
| 151 | if err := brw.Flush(); err != nil { |
| 152 | h.logger.Errorf("flushing response: %v", err) |
| 153 | return |
| 154 | } |
| 155 | |
| 156 | // The server still owns net.Conn. When this handler returns, conn gets closed. |
| 157 | // However, the callback might have to call Close() to abort/unblock any pending/blocking Read(). |
| 158 | h.cb(r.Context(), conn) |
| 159 | }() |
| 160 | |
| 161 | select { |
| 162 | case <-done: |
| 163 | case <-r.Context().Done(): |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | func NewHijackAcceptor(logger logging.Logger, cb func(context.Context, io.ReadWriteCloser)) (string, http.Handler) { |
| 168 | return hijackPath, &hijackHandler{logger: logger, cb: cb, ackTimeout: hijackTimeout} |
no test coverage detected