// createWebSocketStreams returns a context containing the websocket connection and // streams needed to perform an exec or an attach. func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *options, idleTimeout time.Duration) (*context, bool) { channels := createChannels(opts) conn := wsstream.NewConn(channels...) conn.SetIdleTimeout(idleTimeout) streams, err := conn.Open(httplog.Unlogged(w), req) if err != nil { runtime.HandleError(fmt.Errorf("Unable to upgrade websocket connection: %v", err)) return nil, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SPDY and WebSockets and do it outside of this method? switch { case opts.stdout: streams[stdoutChannel].Write([]byte{}) case opts.stderr: streams[stderrChannel].Write([]byte{}) default: streams[errorChannel].Write([]byte{}) } return &context{ conn: conn, stdinStream: streams[stdinChannel], stdoutStream: streams[stdoutChannel], stderrStream: streams[stderrChannel], errorStream: streams[errorChannel], tty: opts.tty, resizeStream: streams[resizeChannel], }, true }
// createWebSocketStreams returns a remoteCommandContext containing the websocket connection and // streams needed to perform an exec or an attach. func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *options, idleTimeout time.Duration) (*context, bool) { // open the requested channels, and always open the error channel channels := append(standardShellChannels(opts.stdin, opts.stdout, opts.stderr), wsstream.WriteChannel) conn := wsstream.NewConn(channels...) conn.SetIdleTimeout(idleTimeout) streams, err := conn.Open(httplog.Unlogged(w), req) if err != nil { glog.Errorf("Unable to upgrade websocket connection: %v", err) return nil, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SPDY and WebSockets and do it outside of this method? switch { case opts.stdout: streams[1].Write([]byte{}) case opts.stderr: streams[2].Write([]byte{}) default: streams[3].Write([]byte{}) } return &context{ conn: conn, stdinStream: streams[0], stdoutStream: streams[1], stderrStream: streams[2], errorStream: streams[3], tty: opts.tty, }, true }
// createWebSocketStreams returns a context containing the websocket connection and // streams needed to perform an exec or an attach. func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *options, idleTimeout time.Duration) (*context, bool) { channels := createChannels(opts) conn := wsstream.NewConn(map[string]wsstream.ChannelProtocolConfig{ "": { Binary: true, Channels: channels, }, preV4BinaryWebsocketProtocol: { Binary: true, Channels: channels, }, preV4Base64WebsocketProtocol: { Binary: false, Channels: channels, }, v4BinaryWebsocketProtocol: { Binary: true, Channels: channels, }, v4Base64WebsocketProtocol: { Binary: false, Channels: channels, }, }) conn.SetIdleTimeout(idleTimeout) negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(w), req) if err != nil { runtime.HandleError(fmt.Errorf("Unable to upgrade websocket connection: %v", err)) return nil, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SPDY and WebSockets and do it outside of this method? switch { case opts.stdout: streams[stdoutChannel].Write([]byte{}) case opts.stderr: streams[stderrChannel].Write([]byte{}) default: streams[errorChannel].Write([]byte{}) } ctx := &context{ conn: conn, stdinStream: streams[stdinChannel], stdoutStream: streams[stdoutChannel], stderrStream: streams[stderrChannel], tty: opts.tty, resizeStream: streams[resizeChannel], } switch negotiatedProtocol { case v4BinaryWebsocketProtocol, v4Base64WebsocketProtocol: ctx.writeStatus = v4WriteStatusFunc(streams[errorChannel]) default: ctx.writeStatus = v1WriteStatusFunc(streams[errorChannel]) } return ctx, true }
func (s *Server) createStreams(request *restful.Request, response *restful.Response) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, Closer, bool, bool) { tty := request.QueryParameter(api.ExecTTYParam) == "1" stdin := request.QueryParameter(api.ExecStdinParam) == "1" stdout := request.QueryParameter(api.ExecStdoutParam) == "1" stderr := request.QueryParameter(api.ExecStderrParam) == "1" if tty && stderr { // TODO: make this an error before we reach this method glog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr") stderr = false } // count the streams client asked for, starting with 1 expectedStreams := 1 if stdin { expectedStreams++ } if stdout { expectedStreams++ } if stderr { expectedStreams++ } if expectedStreams == 1 { response.WriteError(http.StatusBadRequest, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")) return nil, nil, nil, nil, nil, false, false } if wsstream.IsWebSocketRequest(request.Request) { // open the requested channels, and always open the error channel channels := append(standardShellChannels(stdin, stdout, stderr), wsstream.WriteChannel) conn := wsstream.NewConn(channels...) conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) streams, err := conn.Open(httplog.Unlogged(response.ResponseWriter), request.Request) if err != nil { glog.Errorf("Unable to upgrade websocket connection: %v", err) return nil, nil, nil, nil, nil, false, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SDPY and WebSockets and do it outside of this method? switch { case stdout: streams[1].Write([]byte{}) case stderr: streams[2].Write([]byte{}) default: streams[3].Write([]byte{}) } return streams[0], streams[1], streams[2], streams[3], conn, tty, true } supportedStreamProtocols := []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name} _, err := httpstream.Handshake(request.Request, response.ResponseWriter, supportedStreamProtocols, remotecommand.StreamProtocolV1Name) // negotiated protocol isn't used server side at the moment, but could be in the future if err != nil { return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(response.ResponseWriter, request.Request, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(defaultStreamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }