//Create a response body for an HTTP response from a given list of DB changes, including all conflicts private static IDictionary<string, object> ResponseBodyForChanges(RevisionList changes, long since, int limit, DBMonitorCouchbaseResponseState state) { string lastDocId = null; IDictionary<string, object> lastEntry = null; var entries = new List<IDictionary<string, object>>(); foreach (var rev in changes) { string docId = rev.DocID; if (docId.Equals(lastDocId)) { ((IList)lastEntry["changes"]).Add(new Dictionary<string, object> { { "rev", rev.RevID } }); } else { lastEntry = ChangesDictForRev(rev, state); entries.Add(lastEntry); lastDocId = docId; } } entries.Sort((x, y) => (int)((long)x["seq"] - (long)y["seq"])); if (entries.Count > limit) { entries.RemoveRange(limit, entries.Count - limit); } long lastSequence = entries.Any() ? (long)entries.Last()["seq"] : since; return new Dictionary<string, object> { { "results", entries }, { "last_seq", lastSequence } }; }
/// <summary> /// Create a response body for an HTTP response from a given list of DB changes (no conflicts) /// </summary> /// <returns>The response body</returns> /// <param name="changes">The list of changes to be processed</param> /// <param name="since">The first change ID to be processed</param> /// <param name="responseState">The current response state</param> public static IDictionary<string, object> ResponseBodyForChanges(RevisionList changes, long since, DBMonitorCouchbaseResponseState responseState) { List<IDictionary<string, object>> results = new List<IDictionary<string, object>>(); foreach (var change in changes) { results.Add(DatabaseMethods.ChangesDictForRev(change, responseState)); } if (changes.Count > 0) { since = changes.Last().Sequence; } return new Dictionary<string, object> { { "results", results }, { "last_seq", since } }; }
/// <summary> /// Creates a dictionary of metadata for one specific revision /// </summary> /// <returns>The metadata dictionary</returns> /// <param name="rev">The revision to examine</param> /// <param name="responseState">The current response state</param> public static IDictionary<string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState) { if (responseState.ChangesIncludeDocs) { var status = new Status(); var rev2 = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status); if (rev2 != null) { rev2.Sequence = rev.Sequence; rev = rev2; } } return new NonNullDictionary<string, object> { { "seq", rev.Sequence }, { "id", rev.DocID }, { "changes", new List<object> { new Dictionary<string, object> { { "rev", rev.RevID } } } }, { "deleted", rev.Deleted ? (object)true : null }, { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null } }; }
/// <summary> /// Returns a sorted list of changes made to documents in the database, in time order of application. /// </summary> /// <returns>The response state for further HTTP processing</returns> /// <param name="context">The context of the Couchbase Lite HTTP request</param> /// <remarks> /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes /// </remarks> public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context) { DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState(); var responseObject = PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); responseState.Response = response; if (context.ChangesFeedMode < ChangesFeedMode.Continuous) { if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) { response.InternalStatus = StatusCode.NotModified; return response; } } var options = ChangesOptions.Default; responseState.Db = db; responseState.ContentOptions = context.ContentOptions; responseState.ChangesFeedMode = context.ChangesFeedMode; responseState.ChangesIncludeDocs = context.GetQueryParam<bool>("include_docs", bool.TryParse, false); options.IncludeDocs = responseState.ChangesIncludeDocs; responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs"; options.IncludeConflicts = responseState.ChangesIncludeConflicts; options.ContentOptions = context.ContentOptions; options.SortBySequence = !options.IncludeConflicts; options.Limit = context.GetQueryParam<int>("limit", int.TryParse, options.Limit); int since = context.GetQueryParam<int>("since", int.TryParse, 0); string filterName = context.GetQueryParam("filter"); if(filterName != null) { Status status = new Status(); responseState.ChangesFilter = db.GetFilter(filterName, status); if(responseState.ChangesFilter == null) { return context.CreateResponse(status.Code); } responseState.FilterParams = context.GetQueryParams(); } RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams); if((context.ChangesFeedMode >= ChangesFeedMode.Continuous) || (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) { // Response is going to stay open (continuous, or hanging GET): response.Chunked = true; if(context.ChangesFeedMode == ChangesFeedMode.EventSource) { response["Content-Type"] = "text/event-stream; charset=utf-8"; } if(context.ChangesFeedMode >= ChangesFeedMode.Continuous) { response.WriteHeaders(); foreach(var rev in changes) { var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode); if(!success) { return context.CreateResponse(StatusCode.BadRequest); } } } responseState.SubscribeToDatabase(db); string heartbeatParam = context.GetQueryParam("heartbeat"); if(heartbeatParam != null) { int heartbeat; if(!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0) { responseState.IsAsync = false; return context.CreateResponse(StatusCode.BadParam); } var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat); if(heartbeatSpan < MinHeartbeat) { heartbeatSpan = MinHeartbeat; } string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n"; responseState.StartHeartbeat(heartbeatResponse, heartbeatSpan); } return response; } else { if(responseState.ChangesIncludeConflicts) { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState)); } else { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState)); } return response; } }); responseState.Response = responseObject; return responseState; }
public static ICouchbaseResponseState GetChangesPost(ICouchbaseListenerContext context) { DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState(); var responseObject = PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); responseState.Response = response; var body = context.BodyAs<Dictionary<string, object>>(); ProcessBody(body); if (body.GetCast<ChangesFeedMode>("feed") < ChangesFeedMode.Continuous) { if(context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) { response.InternalStatus = StatusCode.NotModified; return response; } } var options = ChangesOptions.Default; responseState.Db = db; responseState.ContentOptions = body.GetCast<DocumentContentOptions>("content_options"); responseState.ChangesFeedMode = body.GetCast<ChangesFeedMode>("feed"); responseState.ChangesIncludeDocs = body.GetCast<bool>("include_docs"); options.IncludeDocs = responseState.ChangesIncludeDocs; responseState.ChangesIncludeConflicts = body.GetCast<string>("style") == "all_docs"; options.IncludeConflicts = responseState.ChangesIncludeConflicts; options.ContentOptions = responseState.ContentOptions; options.SortBySequence = !options.IncludeConflicts; options.Limit = body.GetCast<int>("limit", options.Limit); int since = body.GetCast<int>("since"); string filterName = body.GetCast<string>("filter"); if(filterName != null) { Status status = new Status(); responseState.ChangesFilter = db.GetFilter(filterName, status); if(responseState.ChangesFilter == null) { return context.CreateResponse(status.Code); } responseState.FilterParams = context.GetQueryParams(); } RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams); if((responseState.ChangesFeedMode >= ChangesFeedMode.Continuous) || (responseState.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) { // Response is going to stay open (continuous, or hanging GET): response.Chunked = true; if(responseState.ChangesFeedMode == ChangesFeedMode.EventSource) { response["Content-Type"] = "text/event-stream; charset=utf-8"; } if(responseState.ChangesFeedMode >= ChangesFeedMode.Continuous) { response.WriteHeaders(); foreach(var rev in changes) { response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode); } } responseState.SubscribeToDatabase(db); int heartbeat = body.GetCast<int>("heartbeat", Int32.MinValue); if(heartbeat != Int32.MinValue) { if(heartbeat <= 0) { responseState.IsAsync = false; return context.CreateResponse(StatusCode.BadParam); } heartbeat = Math.Max(heartbeat, (int)MinHeartbeat.TotalMilliseconds); string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n"; responseState.StartHeartbeat(heartbeatResponse, TimeSpan.FromMilliseconds(heartbeat)); } return context.CreateResponse(); } else { if(responseState.ChangesIncludeConflicts) { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState)); } else { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState)); } return response; } }); responseState.Response = responseObject; return responseState; }
/// <summary> /// Creates a dictionary of metadata for one specific revision /// </summary> /// <returns>The metadata dictionary</returns> /// <param name="rev">The revision to examine</param> /// <param name="responseState">The current response state</param> public static IDictionary <string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState) { if (responseState.ChangesIncludeDocs) { var status = new Status(); var rev2 = DocumentMethods.ApplyOptions(responseState.ContentOptions, rev, responseState.Context, responseState.Db, status); if (rev2 != null) { rev2.SetSequence(rev.GetSequence()); rev = rev2; } } return(new NonNullDictionary <string, object> { { "seq", rev.GetSequence() }, { "id", rev.GetDocId() }, { "changes", new List <object> { new Dictionary <string, object> { { "rev", rev.GetRevId() } } } }, { "deleted", rev.IsDeleted() ? (object)true : null }, { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null } }); }
//Create a response body for an HTTP response from a given list of DB changes, including all conflicts private static IDictionary <string, object> ResponseBodyForChanges(RevisionList changes, long since, int limit, DBMonitorCouchbaseResponseState state) { string lastDocId = null; IDictionary <string, object> lastEntry = null; var entries = new List <IDictionary <string, object> >(); foreach (var rev in changes) { string docId = rev.GetDocId(); if (docId.Equals(lastDocId)) { ((IList)lastEntry["changes"]).Add(new Dictionary <string, object> { { "rev", rev.GetRevId() } }); } else { lastEntry = ChangesDictForRev(rev, state); entries.Add(lastEntry); lastDocId = docId; } } entries.Sort((x, y) => (int)((long)x["seq"] - (long)y["seq"])); if (entries.Count > limit) { entries.RemoveRange(limit, entries.Count - limit); } long lastSequence = entries.Any() ? (long)entries.Last()["seq"] : since; return(new Dictionary <string, object> { { "results", entries }, { "last_seq", lastSequence } }); }
/// <summary> /// Create a response body for an HTTP response from a given list of DB changes (no conflicts) /// </summary> /// <returns>The response body</returns> /// <param name="changes">The list of changes to be processed</param> /// <param name="since">The first change ID to be processed</param> /// <param name="responseState">The current response state</param> public static IDictionary <string, object> ResponseBodyForChanges(RevisionList changes, long since, DBMonitorCouchbaseResponseState responseState) { List <IDictionary <string, object> > results = new List <IDictionary <string, object> >(); foreach (var change in changes) { results.Add(DatabaseMethods.ChangesDictForRev(change, responseState)); } if (changes.Count > 0) { since = changes.Last().GetSequence(); } return(new Dictionary <string, object> { { "results", results }, { "last_seq", since } }); }
/// <summary> /// Returns a sorted list of changes made to documents in the database, in time order of application. /// </summary> /// <returns>The response state for further HTTP processing</returns> /// <param name="context">The context of the Couchbase Lite HTTP request</param> /// <remarks> /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes /// <remarks> public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context) { DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState(); var responseObject = PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); responseState.Response = response; if (context.ChangesFeedMode < ChangesFeedMode.Continuous) { if (context.CacheWithEtag(db.LastSequenceNumber.ToString())) { response.InternalStatus = StatusCode.NotModified; return(response); } } var options = ChangesOptions.Default; responseState.Db = db; responseState.ContentOptions = context.ContentOptions; responseState.ChangesFeedMode = context.ChangesFeedMode; responseState.ChangesIncludeDocs = context.GetQueryParam <bool>("include_docs", bool.TryParse, false); options.IncludeDocs = responseState.ChangesIncludeDocs; responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs"; options.IncludeConflicts = responseState.ChangesIncludeConflicts; options.ContentOptions = context.ContentOptions; options.SortBySequence = !options.IncludeConflicts; options.Limit = context.GetQueryParam <int>("limit", int.TryParse, options.Limit); int since = context.GetQueryParam <int>("since", int.TryParse, 0); string filterName = context.GetQueryParam("filter"); if (filterName != null) { Status status = new Status(); responseState.ChangesFilter = db.GetFilter(filterName, status); if (responseState.ChangesFilter == null) { return(context.CreateResponse(status.Code)); } responseState.FilterParams = context.GetQueryParams(); } RevisionList changes = db.ChangesSince(since, options, responseState.ChangesFilter, responseState.FilterParams); if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) || (context.ChangesFeedMode == ChangesFeedMode.LongPoll && changes.Count == 0)) { // Response is going to stay open (continuous, or hanging GET): response.Chunked = true; if (context.ChangesFeedMode == ChangesFeedMode.EventSource) { response["Content-Type"] = "text/event-stream; charset=utf-8"; } if (context.ChangesFeedMode >= ChangesFeedMode.Continuous) { response.WriteHeaders(); foreach (var rev in changes) { response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode); } } responseState.SubscribeToDatabase(db); string heartbeatParam = context.GetQueryParam("heartbeat"); if (heartbeatParam != null) { int heartbeat; if (!int.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0) { responseState.IsAsync = false; return(context.CreateResponse(StatusCode.BadParam)); } heartbeat = Math.Min(heartbeat, MIN_HEARTBEAT); string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n"; responseState.StartHeartbeat(heartbeatResponse, heartbeat); } return(context.CreateResponse()); } else { if (responseState.ChangesIncludeConflicts) { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, options.Limit, responseState)); } else { response.JsonBody = new Body(ResponseBodyForChanges(changes, since, responseState)); } return(response); } }); responseState.Response = responseObject; return(responseState); }
public static ICouchbaseResponseState GetChangesPost(ICouchbaseListenerContext context) { DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState(); var responseObject = PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); responseState.Response = response; var body = context.BodyAs <Dictionary <string, object> >(); ProcessBody(body); if (body.GetCast <ChangesFeedMode>("feed") < ChangesFeedMode.Continuous) { if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) { response.InternalStatus = StatusCode.NotModified; return(response); } } var options = ChangesOptions.Default; responseState.Db = db; responseState.ContentOptions = body.GetCast <DocumentContentOptions>("content_options"); responseState.ChangesFeedMode = body.GetCast <ChangesFeedMode>("feed"); responseState.ChangesIncludeDocs = body.GetCast <bool>("include_docs"); options.IncludeDocs = responseState.ChangesIncludeDocs; responseState.ChangesIncludeConflicts = body.GetCast <string>("style") == "all_docs"; options.IncludeConflicts = responseState.ChangesIncludeConflicts; options.ContentOptions = responseState.ContentOptions; options.SortBySequence = !options.IncludeConflicts; options.Limit = body.GetCast <int>("limit", options.Limit); int since = body.GetCast <int>("since"); string filterName = body.GetCast <string>("filter"); if (filterName != null) { Status status = new Status(); responseState.ChangesFilter = db.GetFilter(filterName, status); if (responseState.ChangesFilter == null) { return(context.CreateResponse(status.Code)); } responseState.FilterParams = context.GetQueryParams(); } if (responseState.ChangesFeedMode >= ChangesFeedMode.LongPoll) { // Response is going to stay open (continuous, or hanging GET): response.Chunked = true; if (responseState.ChangesFeedMode == ChangesFeedMode.EventSource) { response["Content-Type"] = "text/event-stream; charset=utf-8"; } response.WriteHeaders(); if (responseState.SubscribeToDatabase(db, since, options)) { int heartbeat = body.GetCast <int>("heartbeat", Int32.MinValue); if (heartbeat != Int32.MinValue) { if (heartbeat <= 0) { responseState.IsAsync = false; return(context.CreateResponse(StatusCode.BadParam)); } heartbeat = Math.Max(heartbeat, (int)MinHeartbeat.TotalMilliseconds); string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n"; responseState.StartHeartbeat(heartbeatResponse, TimeSpan.FromMilliseconds(heartbeat)); } } return(responseState.Response); } else { var changes = db.ChangesSinceStreaming(since, options, responseState.ChangesFilter, responseState.FilterParams); response.Chunked = true; response.Headers["Content-Type"] = "application/json"; response.WriteBodyCallback = WriteChangesBodyJson; if (responseState.ChangesIncludeConflicts) { response.WriteBodyContext = new WriteChangesContext { IncludeConflicts = true, Since = since, Limit = options.Limit, Changes = changes, ResponseState = responseState }; } else { response.WriteBodyContext = new WriteChangesContext { Since = since, Changes = changes, ResponseState = responseState }; } return(response); } }); responseState.Response = responseObject; return(responseState); }
/// <summary> /// Returns a sorted list of changes made to documents in the database, in time order of application. /// </summary> /// <returns>The response state for further HTTP processing</returns> /// <param name="context">The context of the Couchbase Lite HTTP request</param> /// <remarks> /// http://docs.couchdb.org/en/latest/api/database/changes.html#get--db-_changes /// </remarks> public static ICouchbaseResponseState GetChanges(ICouchbaseListenerContext context) { DBMonitorCouchbaseResponseState responseState = new DBMonitorCouchbaseResponseState(); var responseObject = PerformLogicWithDatabase(context, true, db => { var response = context.CreateResponse(); responseState.Response = response; if (context.ChangesFeedMode < ChangesFeedMode.Continuous) { if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) { response.InternalStatus = StatusCode.NotModified; return(response); } } var options = ChangesOptions.Default; responseState.Db = db; responseState.ContentOptions = context.ContentOptions; responseState.ChangesFeedMode = context.ChangesFeedMode; responseState.ChangesIncludeDocs = context.GetQueryParam <bool>("include_docs", bool.TryParse, false); options.IncludeDocs = responseState.ChangesIncludeDocs; responseState.ChangesIncludeConflicts = context.GetQueryParam("style") == "all_docs"; options.IncludeConflicts = responseState.ChangesIncludeConflicts; options.ContentOptions = context.ContentOptions; options.SortBySequence = !options.IncludeConflicts; options.Limit = context.GetQueryParam <int>("limit", int.TryParse, options.Limit); int since = context.GetQueryParam <int>("since", int.TryParse, 0); string filterName = context.GetQueryParam("filter"); if (filterName != null) { Status status = new Status(); responseState.ChangesFilter = db.GetFilter(filterName, status); if (responseState.ChangesFilter == null) { var code = Database.FilterCompiler == null ? StatusCode.NotImplemented : StatusCode.NotFound; return(context.CreateResponse(code)); } responseState.FilterParams = context.GetQueryParams(); } string heartbeatParam = context.GetQueryParam("heartbeat"); int heartbeat = 0; if (heartbeatParam != null && (!Int32.TryParse(heartbeatParam, out heartbeat) || heartbeat <= 0)) { return(context.CreateResponse(StatusCode.BadParam)); } var changes = db.ChangesSinceStreaming(since, options, responseState.ChangesFilter, responseState.FilterParams); if ((context.ChangesFeedMode >= ChangesFeedMode.Continuous) || (context.ChangesFeedMode == ChangesFeedMode.LongPoll && !changes.Any())) { // Response is going to stay open (continuous, or hanging GET): response.Chunked = true; if (context.ChangesFeedMode == ChangesFeedMode.EventSource) { response["Content-Type"] = "text/event-stream; charset=utf-8"; } if (context.ChangesFeedMode >= ChangesFeedMode.Continuous) { response.WriteHeaders(); foreach (var rev in changes) { var success = response.SendContinuousLine(ChangesDictForRev(rev, responseState), context.ChangesFeedMode); if (!success) { return(context.CreateResponse(StatusCode.BadRequest)); } } } var subscribed = responseState.SubscribeToDatabase(db, since, options); if (!subscribed) { return(response); } if (heartbeat > 0) { var heartbeatSpan = TimeSpan.FromMilliseconds(heartbeat); if (heartbeatSpan < MinHeartbeat) { heartbeatSpan = MinHeartbeat; } string heartbeatResponse = context.ChangesFeedMode == ChangesFeedMode.EventSource ? "\n\n" : "\r\n"; responseState.StartHeartbeat(heartbeatResponse, heartbeatSpan); } return(response); } else { response.Chunked = true; response.Headers["Content-Type"] = "application/json"; response.WriteBodyCallback = WriteChangesBodyJson; if (responseState.ChangesIncludeConflicts) { response.WriteBodyContext = new WriteChangesContext { IncludeConflicts = true, Since = since, Limit = options.Limit, Changes = changes, ResponseState = responseState }; } else { response.WriteBodyContext = new WriteChangesContext { Since = since, Changes = changes, ResponseState = responseState }; } } return(response); }); responseState.Response = responseObject; return(responseState); }
/// <summary> /// Creates a dictionary of metadata for one specific revision /// </summary> /// <returns>The metadata dictionary</returns> /// <param name="rev">The revision to examine</param> /// <param name="responseState">The current response state</param> public static IDictionary <string, object> ChangesDictForRev(RevisionInternal rev, DBMonitorCouchbaseResponseState responseState) { return(new NonNullDictionary <string, object> { { "seq", rev.GetSequence() }, { "id", rev.GetDocId() }, { "changes", new List <object> { new Dictionary <string, object> { { "rev", rev.GetRevId() } } } }, { "deleted", rev.IsDeleted() ? (object)true : null }, { "doc", responseState.ChangesIncludeDocs ? rev.GetProperties() : null } }); }