// Calls the JS sync function to assign the doc to channels, grant users // access to channels, and reject invalid documents. func (db *Database) getChannelsAndAccess(doc *document, body Body, parentRevID string) (result base.Set, access channels.AccessMap, roles channels.AccessMap, err error) { base.LogTo("CRUD+", "Invoking sync on doc %q rev %s", doc.ID, body["_rev"]) // Get the parent revision, to pass to the sync function: var oldJson string if parentRevID != "" { var oldJsonBytes []byte oldJsonBytes, err = db.getRevisionJSON(doc, parentRevID) if err != nil { if base.IsDocNotFoundError(err) { err = nil } return } oldJson = string(oldJsonBytes) } if db.ChannelMapper != nil { // Call the ChannelMapper: var output *channels.ChannelMapperOutput output, err = db.ChannelMapper.MapToChannelsAndAccess(body, oldJson, makeUserCtx(db.user)) if err == nil { result = output.Channels access = output.Access roles = output.Roles err = output.Rejection if err != nil { base.Log("Sync fn rejected: new=%+v old=%s --> %s", body, oldJson, err) } else if !validateAccessMap(access) || !validateRoleAccessMap(roles) { err = base.HTTPErrorf(500, "Error in JS sync function") } } else { base.Warn("Sync fn exception: %+v; doc = %s", err, body) err = base.HTTPErrorf(500, "Exception in JS sync function") } } else { // No ChannelMapper so by default use the "channels" property: value, _ := body["channels"].([]interface{}) if value != nil { array := make([]string, 0, len(value)) for _, channel := range value { channelStr, ok := channel.(string) if ok && len(channelStr) > 0 { array = append(array, channelStr) } } result, err = channels.SetFromArray(array, channels.KeepStar) } } return }
// 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 }
// Calls the JS sync function to assign the doc to channels, grant users // access to channels, and reject invalid documents. func (db *Database) getChannelsAndAccess(doc *document, body Body, parentRevID string) (result channels.Set, access channels.AccessMap, err error) { base.LogTo("CRUD", "Invoking sync on doc %q rev %s", doc.ID, body["_rev"]) var oldJson string if parentRevID != "" { oldJson = string(doc.getRevisionJSON(parentRevID)) } if db.ChannelMapper != nil { var output *channels.ChannelMapperOutput output, err = db.ChannelMapper.MapToChannelsAndAccess(body, oldJson, makeUserCtx(db.user)) if err == nil { result = output.Channels access = output.Access err = output.Rejection if err != nil { base.Log("Sync fn rejected: new=%+v old=%s --> %s", body, oldJson, err) } else if !validateAccessMap(access) { err = &base.HTTPError{500, fmt.Sprintf("Error in JS sync function")} } } else { base.Warn("Sync fn exception: %+v; doc = %s", err, body) err = &base.HTTPError{500, "Exception in JS sync function"} } } else { // No ChannelMapper so by default use the "channels" property: value, _ := body["channels"].([]interface{}) if value != nil { array := make([]string, 0, len(value)) for _, channel := range value { channelStr, ok := channel.(string) if ok && len(channelStr) > 0 { array = append(array, channelStr) } } result, err = channels.SetFromArray(array, channels.KeepStar) } } return }
func (h *handler) handleChanges() error { // http://wiki.apache.org/couchdb/HTTP_database_API#Changes var options db.ChangesOptions options.Since = h.getIntQuery("since", 0) options.Limit = int(h.getIntQuery("limit", 0)) options.Conflicts = (h.getQuery("style") == "all_docs") options.IncludeDocs = (h.getBoolQuery("include_docs")) // 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.HTTPError{http.StatusBadRequest, "Unknown filter; try sync_gateway/bychannel"} } channelsParam := h.getQuery("channels") if channelsParam == "" { return &base.HTTPError{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.HTTPError{http.StatusBadRequest, "Empty channel list"} } } switch h.getQuery("feed") { case "continuous": return h.handleContinuousChanges(userChannels, options) case "longpoll": options.Wait = true } return h.handleSimpleChanges(userChannels, options) }
// 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") } }