func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser { buf := bytes.NewBuffer([]byte{}) enc := versioned.NewEncoder(streaming.NewEncoder(buf, codec), codec) for i := range events { enc.Encode(&events[i]) } return json.Framer.NewFrameReader(ioutil.NopCloser(buf)) }
// Serve 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 TestWatch(t *testing.T) { tcs := []struct { name string namespace string events []watch.Event path string }{ { name: "normal_watch", path: "/api/gtest/vtest/watch/rtest", events: []watch.Event{ {Type: watch.Added, Object: getObject("vTest", "rTest", "normal_watch")}, {Type: watch.Modified, Object: getObject("vTest", "rTest", "normal_watch")}, {Type: watch.Deleted, Object: getObject("vTest", "rTest", "normal_watch")}, }, }, { name: "namespaced_watch", namespace: "nstest", path: "/api/gtest/vtest/watch/namespaces/nstest/rtest", events: []watch.Event{ {Type: watch.Added, Object: getObject("vTest", "rTest", "namespaced_watch")}, {Type: watch.Modified, Object: getObject("vTest", "rTest", "namespaced_watch")}, {Type: watch.Deleted, Object: getObject("vTest", "rTest", "namespaced_watch")}, }, }, } for _, tc := range tcs { gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method) } if r.URL.Path != tc.path { t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) } enc := versioned.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{}) for _, e := range tc.events { enc.Encode(&e) } }) if err != nil { t.Errorf("unexpected error when creating client: %v", err) continue } defer srv.Close() watcher, err := cl.Resource(resource, tc.namespace).Watch(&v1.ListOptions{}) if err != nil { t.Errorf("unexpected error when watching %q: %v", tc.name, err) continue } for _, want := range tc.events { got := <-watcher.ResultChan() if !reflect.DeepEqual(got, want) { t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got) } } } }