// 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 }
// 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.TimeoutFactory.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 := &metav1.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 = metav1.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() } } }