func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { reqInfo := newRequestInfo(req) if rt.levels[debugJustURL] { glog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL) } if rt.levels[debugCurlCommand] { glog.Infof("%s", reqInfo.toCurl()) } if rt.levels[debugRequestHeaders] { glog.Infof("Request Headers:") for key, values := range reqInfo.RequestHeaders { for _, value := range values { glog.Infof(" %s: %s", key, value) } } } startTime := time.Now() response, err := rt.delegatedRoundTripper.RoundTrip(req) reqInfo.Duration = time.Since(startTime) reqInfo.complete(response, err) if rt.levels[debugURLTiming] { glog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond)) } if rt.levels[debugResponseStatus] { glog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond)) } if rt.levels[debugResponseHeaders] { glog.Infof("Response Headers:") for key, values := range reqInfo.ResponseHeaders { for _, value := range values { glog.Infof(" %s: %s", key, value) } } } return response, err }
// getListener creates a listener on the interface targeted by the given hostname on the given port with // the given protocol. protocol is in net.Listen style which basically admits values like tcp, tcp4, tcp6 func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) { listener, err := net.Listen(protocol, fmt.Sprintf("%s:%d", hostname, port.Local)) if err != nil { util.HandleError(fmt.Errorf("Unable to create listener: Error %s", err)) return nil, err } listenerAddress := listener.Addr().String() host, localPort, _ := net.SplitHostPort(listenerAddress) localPortUInt, err := strconv.ParseUint(localPort, 10, 16) if err != nil { return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host) } port.Local = uint16(localPortUInt) glog.Infof("Forwarding from %s:%d -> %d", hostname, localPortUInt, port.Remote) return listener, nil }
// handleConnection copies data between the local connection and the stream to // the remote server. func (pf *PortForwarder) handleConnection(conn net.Conn, port ForwardedPort) { defer conn.Close() glog.Infof("Handling connection for %d", port.Local) requestID := pf.nextRequestID() // create error stream headers := http.Header{} headers.Set(api.StreamType, api.StreamTypeError) headers.Set(api.PortHeader, fmt.Sprintf("%d", port.Remote)) headers.Set(api.PortForwardRequestIDHeader, strconv.Itoa(requestID)) errorStream, err := pf.streamConn.CreateStream(headers) if err != nil { util.HandleError(fmt.Errorf("error creating error stream for port %d -> %d: %v", port.Local, port.Remote, err)) return } // we're not writing to this stream errorStream.Close() errorChan := make(chan error) go func() { message, err := ioutil.ReadAll(errorStream) switch { case err != nil: errorChan <- fmt.Errorf("error reading from error stream for port %d -> %d: %v", port.Local, port.Remote, err) case len(message) > 0: errorChan <- fmt.Errorf("an error occurred forwarding %d -> %d: %v", port.Local, port.Remote, string(message)) } close(errorChan) }() // create data stream headers.Set(api.StreamType, api.StreamTypeData) dataStream, err := pf.streamConn.CreateStream(headers) if err != nil { util.HandleError(fmt.Errorf("error creating forwarding stream for port %d -> %d: %v", port.Local, port.Remote, err)) return } localError := make(chan struct{}) remoteDone := make(chan struct{}) go func() { // Copy from the remote side to the local port. if _, err := io.Copy(conn, dataStream); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { util.HandleError(fmt.Errorf("error copying from remote stream to local connection: %v", err)) } // inform the select below that the remote copy is done close(remoteDone) }() go func() { // inform server we're not sending any more data after copy unblocks defer dataStream.Close() // Copy from the local port to the remote side. if _, err := io.Copy(dataStream, conn); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { util.HandleError(fmt.Errorf("error copying from local connection to remote stream: %v", err)) // break out of the select below without waiting for the other copy to finish close(localError) } }() // wait for either a local->remote error or for copying from remote->local to finish select { case <-remoteDone: case <-localError: } // always expect something on errorChan (it may be nil) err = <-errorChan if err != nil { util.HandleError(err) } }