// 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 }
// serveWatch handles serving requests to the server func serveWatch(watcher watch.Interface, scope RequestScope, w http.ResponseWriter, req *restful.Request, timeout time.Duration) { watchServer := &WatchServer{watcher, scope.Codec, func(obj runtime.Object) { if err := setSelfLink(obj, req, scope.Namer); err != nil { glog.V(5).Infof("Failed to set self link for object %v: %v", reflect.TypeOf(obj), err) } }, &realTimeoutFactory{timeout}} if isWebsocketRequest(req.Request) { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req.Request) } else { watchServer.ServeHTTP(w, req.Request) } }
// serveWatch handles serving requests to the server func serveWatch(watcher watch.Interface, scope RequestScope, w http.ResponseWriter, req *restful.Request, minRequestTimeout time.Duration) { var timeout time.Duration if minRequestTimeout > 0 { // Each watch gets a random timeout between minRequestTimeout and 2*minRequestTimeout to avoid thundering herds. timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) } watchServer := &WatchServer{watcher, scope.Codec, func(obj runtime.Object) { if err := setSelfLink(obj, req, scope.Namer); err != nil { glog.V(5).Infof("Failed to set self link for object %v: %v", reflect.TypeOf(obj), err) } }, &realTimeoutFactory{timeout}} if isWebsocketRequest(req.Request) { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req.Request) } else { watchServer.ServeHTTP(w, req.Request) } }
// ServeHTTP serves a series of JSON encoded events via straight HTTP with // Transfer-Encoding: chunked. func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { loggedW := httplog.LogOf(req, w) w = httplog.Unlogged(w) timeoutCh, cleanup := self.t.TimeoutCh() defer cleanup() defer self.watching.Stop() cn, ok := w.(http.CloseNotifier) if !ok { loggedW.Addf("unable to get CloseNotifier: %#v", w) http.NotFound(w, req) return } flusher, ok := w.(http.Flusher) if !ok { loggedW.Addf("unable to get Flusher: %#v", w) http.NotFound(w, req) return } w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) flusher.Flush() // TODO: use arbitrary serialization on watch encoder := watchjson.NewEncoder(w, self.encoder) for { select { case <-cn.CloseNotify(): return case <-timeoutCh: return case event, ok := <-self.watching.ResultChan(): if !ok { // End of results. return } self.fixup(event.Object) if err := encoder.Encode(&event); err != nil { // Client disconnect. return } flusher.Flush() } } }
// serveWatch handles serving requests to the server func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) { s, mediaType, err := negotiateOutputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, req, res) return } // TODO: replace with typed serialization if mediaType != "application/json" { writeRawJSON(http.StatusNotAcceptable, (errNotAcceptable{[]string{"application/json"}}).Status(), res.ResponseWriter) return } encoder := scope.Serializer.EncoderForVersion(s, scope.Kind.GroupVersion()) watchServer := &WatchServer{watcher, encoder, func(obj runtime.Object) { if err := setSelfLink(obj, req, scope.Namer); err != nil { glog.V(5).Infof("Failed to set self link for object %v: %v", reflect.TypeOf(obj), err) } }, &realTimeoutFactory{timeout}} if isWebsocketRequest(req.Request) { websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(res.ResponseWriter), req.Request) } else { watchServer.ServeHTTP(res.ResponseWriter, req.Request) } }
// ServeHTTP serves a series of encoded events via HTTP with Transfer-Encoding: chunked // or over a websocket connection. func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { w = httplog.Unlogged(w) if wsstream.IsWebSocketRequest(req) { w.Header().Set("Content-Type", s.mediaType) websocket.Handler(s.HandleWS).ServeHTTP(w, req) return } cn, ok := w.(http.CloseNotifier) if !ok { err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w) utilruntime.HandleError(err) s.scope.err(errors.NewInternalError(err), w, req) return } flusher, ok := w.(http.Flusher) if !ok { err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w) utilruntime.HandleError(err) s.scope.err(errors.NewInternalError(err), w, req) return } framer := s.framer.NewFrameWriter(w) if framer == nil { // programmer error err := fmt.Errorf("no stream framing support is available for media type %q", s.mediaType) utilruntime.HandleError(err) s.scope.err(errors.NewBadRequest(err.Error()), w, req) return } e := streaming.NewEncoder(framer, s.encoder) // ensure the connection times out timeoutCh, cleanup := s.t.TimeoutCh() defer cleanup() defer s.watching.Stop() // begin the stream w.Header().Set("Content-Type", s.mediaType) w.Header().Set("Transfer-Encoding", "chunked") w.WriteHeader(http.StatusOK) flusher.Flush() var unknown runtime.Unknown internalEvent := &versioned.InternalEvent{} buf := &bytes.Buffer{} ch := s.watching.ResultChan() for { select { case <-cn.CloseNotify(): return case <-timeoutCh: return case event, ok := <-ch: if !ok { // End of results. return } obj := event.Object s.fixup(obj) if err := s.embeddedEncoder.Encode(obj, buf); err != nil { // unexpected error utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err)) return } // ContentType is not required here because we are defaulting to the serializer // type unknown.Raw = buf.Bytes() event.Object = &unknown // the internal event will be versioned by the encoder *internalEvent = versioned.InternalEvent(event) if err := e.Encode(internalEvent); err != nil { utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v (%#v)", err, e)) // client disconnect. return } if len(ch) == 0 { flusher.Flush() } buf.Reset() } } }
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 }