// Top-level handler for _changes feed requests. func (h *handler) handleChanges() error { // http://wiki.apache.org/couchdb/HTTP_database_API#Changes // http://docs.couchdb.org/en/latest/api/database/changes.html var options db.ChangesOptions options.Since = channels.TimedSetFromString(h.getQuery("since")) options.Limit = int(h.getIntQuery("limit", 0)) options.Conflicts = (h.getQuery("style") == "all_docs") options.IncludeDocs = (h.getBoolQuery("include_docs")) options.Terminator = make(chan bool) defer close(options.Terminator) // Get the channels as parameters to an imaginary "bychannel" filter. // The default is all channels the user can access. userChannels := channels.SetOf("*") filter := h.getQuery("filter") if filter != "" { if filter != "sync_gateway/bychannel" { return base.HTTPErrorf(http.StatusBadRequest, "Unknown filter; try sync_gateway/bychannel") } channelsParam := h.getQuery("channels") if channelsParam == "" { return base.HTTPErrorf(http.StatusBadRequest, "Missing 'channels' filter parameter") } var err error userChannels, err = channels.SetFromArray(strings.Split(channelsParam, ","), channels.ExpandStar) if err != nil { return err } if len(userChannels) == 0 { return base.HTTPErrorf(http.StatusBadRequest, "Empty channel list") } } h.db.ChangesClientStats.Increment() defer h.db.ChangesClientStats.Decrement() switch h.getQuery("feed") { case "normal", "": return h.sendSimpleChanges(userChannels, options) case "longpoll": options.Wait = true return h.sendSimpleChanges(userChannels, options) case "continuous": return h.sendContinuousChangesByHTTP(userChannels, options) case "websocket": return h.sendContinuousChangesByWebSocket(userChannels, options) default: return base.HTTPErrorf(http.StatusBadRequest, "Unknown feed type") } }
func (h *handler) sendContinuousChangesByWebSocket(inChannels base.Set, options db.ChangesOptions) error { handler := func(conn *websocket.Conn) { h.logStatus(101, "Upgraded to WebSocket protocol") defer func() { conn.Close() base.LogTo("HTTP+", "#%03d: --> WebSocket closed", h.serialNumber) }() // Read changes-feed options from an initial incoming WebSocket message in JSON format: if msg, err := readWebSocketMessage(conn); err != nil { return } else { var channelNames []string var err error if _, options, _, channelNames, err = readChangesOptionsFromJSON(msg); err != nil { return } if channelNames != nil { inChannels, _ = channels.SetFromArray(channelNames, channels.ExpandStar) } } options.Terminator = make(chan bool) defer close(options.Terminator) caughtUp := false h.generateContinuousChanges(inChannels, options, func(changes []*db.ChangeEntry) error { var data []byte if changes != nil { data, _ = json.Marshal(changes) } else if !caughtUp { caughtUp = true data, _ = json.Marshal([]*db.ChangeEntry{}) } else { data = []byte{} } _, err := conn.Write(data) return err }) } server := websocket.Server{ Handshake: func(*websocket.Config, *http.Request) error { return nil }, Handler: handler, } server.ServeHTTP(h.response, h.rq) return nil }
// Top-level handler for _changes feed requests. Accepts GET or POST requests. func (h *handler) handleChanges() error { // http://wiki.apache.org/couchdb/HTTP_database_API#Changes // http://docs.couchdb.org/en/latest/api/database/changes.html restExpvars.Add("changesFeeds_total", 1) restExpvars.Add("changesFeeds_active", 1) defer restExpvars.Add("changesFeeds_active", -1) var feed string var options db.ChangesOptions var filter string var channelsArray []string if h.rq.Method == "GET" { // GET request has parameters in URL: feed = h.getQuery("feed") options.Since = channels.TimedSetFromString(h.getQuery("since")) options.Limit = int(h.getIntQuery("limit", 0)) options.Conflicts = (h.getQuery("style") == "all_docs") options.IncludeDocs = (h.getBoolQuery("include_docs")) filter = h.getQuery("filter") channelsParam := h.getQuery("channels") if channelsParam != "" { channelsArray = strings.Split(channelsParam, ",") } } else { // POST request has parameters in JSON body: body, err := h.readBody() if err != nil { return err } feed, options, filter, channelsArray, err = readChangesOptionsFromJSON(body) if err != nil { return err } } // Get the channels as parameters to an imaginary "bychannel" filter. // The default is all channels the user can access. userChannels := channels.SetOf("*") if filter != "" { if filter != "sync_gateway/bychannel" { return base.HTTPErrorf(http.StatusBadRequest, "Unknown filter; try sync_gateway/bychannel") } if channelsArray == nil { return base.HTTPErrorf(http.StatusBadRequest, "Missing 'channels' filter parameter") } var err error userChannels, err = channels.SetFromArray(channelsArray, channels.ExpandStar) if err != nil { return err } if len(userChannels) == 0 { return base.HTTPErrorf(http.StatusBadRequest, "Empty channel list") } } h.db.ChangesClientStats.Increment() defer h.db.ChangesClientStats.Decrement() options.Terminator = make(chan bool) defer close(options.Terminator) switch feed { case "normal", "": return h.sendSimpleChanges(userChannels, options) case "longpoll": options.Wait = true return h.sendSimpleChanges(userChannels, options) case "continuous": return h.sendContinuousChangesByHTTP(userChannels, options) case "websocket": return h.sendContinuousChangesByWebSocket(userChannels, options) default: return base.HTTPErrorf(http.StatusBadRequest, "Unknown feed type") } }