public void Can_roundtrip_DispatchItem_with_complex_event() { var body = new XDoc("msg").Elem("foo", "bar"); var channel = new XUri("channel://foo.com/bar"); var resource = new XUri("http://foo.com/baz/0"); var origin1 = new XUri("http://foo.com/baz/1"); var origin2 = new XUri("http://foo.com/baz/2"); var recipient1 = new DispatcherRecipient(new XUri("http://recipient1")); var recipient2 = new DispatcherRecipient(new XUri("http://recipient2")); var via1 = new XUri("http://via1"); var via2 = new XUri("http://via2"); var ev = new DispatcherEvent(body, channel, resource, origin1, origin2); ev = ev.WithRecipient(false, recipient1, recipient2).WithVia(via1).WithVia(via2); var item = new DispatchItem( new XUri("http://foo"), ev, "abc" ); var serializer = new DispatchItemSerializer(); var stream = serializer.ToStream(item); var item2 = serializer.FromStream(stream); Assert.AreEqual(item.Uri, item2.Uri, "uri mismatch"); Assert.AreEqual(item.Location, item2.Location, "location mismatch"); Assert.AreEqual(item.Event.Id, item2.Event.Id, "id mismatch"); Assert.AreEqual(body.ToCompactString(), item2.Event.AsDocument().ToCompactString(), "body mismatch"); Assert.AreEqual(channel, item2.Event.Channel, "channel mismatch"); Assert.AreEqual(resource, item2.Event.Resource, "resource mismatch"); Assert.AreEqual(origin1, item2.Event.Origins[0], "first origin mismatch"); Assert.AreEqual(origin2, item2.Event.Origins[1], "second origin mismatch"); Assert.AreEqual(recipient1.Uri, item2.Event.Recipients[0].Uri, "first recipient mismatch"); Assert.AreEqual(recipient2.Uri, item2.Event.Recipients[1].Uri, "second recipient mismatch"); Assert.AreEqual(via1, item2.Event.Via[0], "first via mismatch"); Assert.AreEqual(via2, item2.Event.Via[1], "second via mismatch"); }
public void Add_recipients_to_event() { XDoc msg = new XDoc("msg"); DispatcherRecipient r1 = new DispatcherRecipient(new XUri("mailto:///[email protected]")); DispatcherRecipient r2 = new DispatcherRecipient(new XUri("mailto:///[email protected]")); DispatcherRecipient r3 = new DispatcherRecipient(new XUri("mailto:///[email protected]")); DispatcherEvent ev1 = new DispatcherEvent(msg, new XUri("channel://foo.com/bar"), new XUri("http://foo.com/baz")); DispatcherEvent ev2 = ev1.WithRecipient(false, r1); Assert.AreEqual(0, ev1.Recipients.Length); Assert.AreEqual(1, ev2.Recipients.Length); Assert.AreEqual(r1, ev2.Recipients[0]); DispatcherEvent ev3 = ev2.WithRecipient(false, r2, r3); Assert.AreEqual(3, ev3.Recipients.Length); Assert.AreEqual(r1, ev3.Recipients[0]); Assert.AreEqual(r2, ev3.Recipients[1]); Assert.AreEqual(r3, ev3.Recipients[2]); DreamMessage ev3msg = ev3.AsMessage(); Assert.AreEqual(msg, ev3msg.ToDocument()); Assert.AreEqual(ev1.Id, ev3.Id); Assert.AreEqual("channel://foo.com/bar", ev3msg.Headers.DreamEventChannel); Assert.AreEqual("http://foo.com/baz", ev3msg.Headers.DreamEventOrigin[0]); string[] recipients = ev3msg.Headers.DreamEventRecipients; Assert.AreEqual(3, recipients.Length); Assert.AreEqual(r1.ToString(), recipients[0]); Assert.AreEqual(r2.ToString(), recipients[1]); Assert.AreEqual(r3.ToString(), recipients[2]); }
/// <summary> /// Override hook for modifying the selection of event listeners based on recipients subscribed to the event. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield GetListenersForRecipients(DispatcherEvent ev, Result <Dictionary <XUri, List <DispatcherRecipient> > > result) { //if the event has recipients attached, do subscription lookup by recipients _log.Debug("trying dispatch based on event recipient list event"); lock (_destinationsByRecipient) { Dictionary <XUri, List <DispatcherRecipient> > listeners = new Dictionary <XUri, List <DispatcherRecipient> >(); foreach (DispatcherRecipient recipient in ev.Recipients) { List <XUri> destinations; if (_destinationsByRecipient.TryGetValue(recipient, out destinations)) { foreach (XUri destination in destinations) { List <DispatcherRecipient> recipients; if (!listeners.TryGetValue(destination, out recipients)) { recipients = new List <DispatcherRecipient>(); listeners.Add(destination, recipients); } if (!recipients.Contains(recipient)) { recipients.Add(recipient); } } } } result.Return(listeners); } yield break; }
/// <summary> /// Override hook for modifying the selection of event listeners based on channel and resource matches. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result <Dictionary <XUri, List <DispatcherRecipient> > > result) { // dispatch to all subscriptions that listen for this event and its contents _log.Debug("trying dispatch based on channel matches"); ICollection <PubSubSubscription> listeningSubs = null; lock (_channelMap) { if (ev.Resource != null) { listeningSubs = _resourceMap.GetMatches(ev.Resource); } listeningSubs = _channelMap.GetMatches(ev.Channel, listeningSubs); } Dictionary <XUri, List <DispatcherRecipient> > listeners = new Dictionary <XUri, List <DispatcherRecipient> >(); foreach (PubSubSubscription sub in listeningSubs) { List <DispatcherRecipient> recipients; if (!listeners.TryGetValue(sub.Destination, out recipients)) { recipients = new List <DispatcherRecipient>(); listeners.Add(sub.Destination, recipients); } foreach (DispatcherRecipient recipient in sub.Recipients) { if (!recipients.Contains(recipient)) { recipients.Add(recipient); } } } result.Return(listeners); yield break; }
private DispatcherEvent(DispatcherEvent ev, XUri[] via, DispatcherRecipient[] recipients) { _message = ev._message; Id = ev.Id; Channel = ev.Channel; Resource = ev.Resource; Origins = ev.Origins; Recipients = recipients; Via = via; }
/// <summary> /// Override hook for filtering recipients for an event. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="recipients">List of proposed recipients.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result <DispatcherEvent> result) { if (ev.Recipients.Length == 0) { //if the event has no reciepient list, anyone can receive it result.Return(ev); yield break; } recipients = ArrayUtil.Intersect(ev.Recipients, recipients); result.Return(recipients.Length == 0 ? null : ev.WithRecipient(true, recipients)); yield break; }
private void DispatchFromQueue(DispatcherEvent dispatchEvent, Action completionCallback) { Coroutine.Invoke(Dispatch_Helper, dispatchEvent, new Result()).WhenDone( r => { completionCallback(); if (r.HasException) { _log.ErrorExceptionMethodCall(r.Exception, "AsyncDispatcher", "async queue processor encountered an error"); } else { _log.DebugFormat("finished dispatch of event '{0}'", dispatchEvent.Id); } }); }
private void Update_Helper() { lock (_subscriptionsByOwner) { PubSubSubscription[] allSubs = CalculateCombinedSubscriptions(); _combinedSetVersion++; _combinedSet = new PubSubSubscriptionSet(_owner, _combinedSetVersion, _serviceKeySetCookie, allSubs); } // this is outside the lock, since it may be an expensive operation and shouldn't block lookups DispatcherEvent ev = new DispatcherEvent(_combinedSet.AsDocument(), new XUri("pubsub:///set/update"), null, _owner); _log.DebugFormat("updated combined set, dispatching as id '{0}'", ev.Id); Dispatch(ev); if (CombinedSetUpdated != null) { CombinedSetUpdated(this, EventArgs.Empty); } }
public void Can_roundtrip_DispatchItem() { var msg = new XDoc("msg"); var channel = new XUri("channel://foo.com/bar"); var origin = new XUri("http://foo.com/baz"); var ev = new DispatcherEvent(msg, channel, origin); var item = new DispatchItem( new XUri("http://foo"), ev, "abc" ); var serializer = new DispatchItemSerializer(); var stream = serializer.ToStream(item); var item2 = serializer.FromStream(stream); Assert.AreEqual(item.Uri, item2.Uri, "uri mismatch"); Assert.AreEqual(item.Location, item2.Location, "location mismatch"); Assert.AreEqual(item.Event.Id, item2.Event.Id, "id mismatch"); }
public void Add_via_to_event() { XDoc msg = new XDoc("msg"); XUri via1 = new XUri("http://foo.com/route1"); XUri via2 = new XUri("http://foo.com/route2"); DispatcherEvent ev1 = new DispatcherEvent(msg, new XUri("channel://foo.com/bar"), new XUri("http://foo.com/baz")); DispatcherEvent ev2 = ev1.WithVia(via1); Assert.AreEqual(0, ev1.Via.Length); Assert.AreEqual(1, ev2.Via.Length); Assert.AreEqual(via1, ev2.Via[0]); DispatcherEvent ev3 = ev2.WithVia(via2); Assert.AreEqual(2, ev3.Via.Length); Assert.AreEqual(via1, ev3.Via[0]); Assert.AreEqual(via2, ev3.Via[1]); DreamMessage ev3msg = ev3.AsMessage(); Assert.AreEqual(msg, ev3msg.ToDocument()); Assert.AreEqual(ev1.Id, ev3.Id); Assert.AreEqual("channel://foo.com/bar", ev3msg.Headers.DreamEventChannel); Assert.AreEqual("http://foo.com/baz", ev3msg.Headers.DreamEventOrigin[0]); Assert.AreEqual(via1.ToString(), ev3msg.Headers.DreamEventVia[0]); }
private Yield Dispatch_Helper(DispatcherEvent dispatchEvent, Result result) { Dictionary <XUri, List <DispatcherRecipient> > listeningEndpoints = null; if (dispatchEvent.Recipients != null && dispatchEvent.Recipients.Length > 0) { yield return(Coroutine.Invoke(GetListenersForRecipients, dispatchEvent, new Result <Dictionary <XUri, List <DispatcherRecipient> > >()).Set(v => listeningEndpoints = v)); } else { yield return(Coroutine.Invoke(GetListenersByChannelResourceMatch, dispatchEvent, new Result <Dictionary <XUri, List <DispatcherRecipient> > >()).Set(v => listeningEndpoints = v)); } if (listeningEndpoints.Count == 0) { _log.DebugFormat("event '{0}' for resource '{1}' has no endpoints to dispatch to", dispatchEvent.Id, dispatchEvent.Resource); } else { _log.DebugFormat("event '{0}' for resource '{1}' ready for dispatch with {2} endpoint(s)", dispatchEvent.Id, dispatchEvent.Resource, listeningEndpoints.Count); } foreach (KeyValuePair <XUri, List <DispatcherRecipient> > destination in listeningEndpoints) { var uri = destination.Key; DispatcherEvent subEvent = null; yield return(Coroutine.Invoke(DetermineRecipients, dispatchEvent, destination.Value.ToArray(), new Result <DispatcherEvent>()).Set(v => subEvent = v)); if (subEvent == null) { _log.DebugFormat("no recipient union for event '{0}' and {1}", dispatchEvent.Id, uri); continue; } _log.DebugFormat("dispatching event '{0}' to {1}", subEvent.Id, uri); Plug p = Plug.New(uri); p = p.WithCookieJar(_cookieJar); Result <DreamMessage> response = p.Post(subEvent.AsMessage(), new Result <DreamMessage>(TimeSpan.MaxValue)); response.WhenDone(r => DispatchCompletion_Helper(uri, r)); } result.Return(); yield break; }
/// <summary> /// Dispatch an event against the registered subscriptions. /// </summary> /// <param name="ev">Dispatch event instance.</param> public void Dispatch(DispatcherEvent ev) { if (ev.HasVisited(_owner)) { // this event is in a dispatch loop, so we drop it if (_log.IsWarnEnabled) { _log.WarnFormat("event for channel '{0}' already visited the service, dropping", ev.Channel); if (_log.IsDebugEnabled) { _log.Debug(" event origin:"); foreach (XUri origin in ev.Origins) { _log.DebugFormat(" - {0}", origin); } _log.Debug(" event route:"); foreach (XUri via in ev.Via) { _log.DebugFormat(" - {0}", via); } } } throw new DreamBadRequestException("Dispatch loop detected: The event has already been dispatched by this service"); } if (_log.IsDebugEnabled) { _log.DebugFormat("Dispatcher '{0}' dispatching '{1}' on channel '{2}' with resource '{3}'", _owner, ev.Id, ev.Channel, ev.Resource); } DispatcherEvent dispatchEvent = ev.WithVia(_owner); if (!_dispatchQueue.TryEnqueue(dispatchEvent)) { throw new InvalidOperationException(string.Format("Enqueue of '{0}' failed.", dispatchEvent.Id)); } }
/// <summary> /// Override hook for filtering recipients for an event. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="recipients">List of proposed recipients.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result<DispatcherEvent> result) { if(ev.Recipients.Length == 0) { //if the event has no reciepient list, anyone can receive it result.Return(ev); yield break; } recipients = ArrayUtil.Intersect(ev.Recipients, recipients); result.Return(recipients.Length == 0 ? null : ev.WithRecipient(true, recipients)); yield break; }
internal Yield PublishEvent(DreamContext context, DreamMessage request, Result<DreamMessage> response) { DispatcherEvent ev; try { ev = new DispatcherEvent(request); _log.DebugFormat("{0} received event '{1}'", this.Self.Uri, ev.Id); if(ev.Channel.Scheme == "pubsub") { response.Return(DreamMessage.Forbidden("events published into this service cannot be of scheme 'pubsub'")); yield break; } _dispatcher.Dispatch(ev); response.Return(DreamMessage.Ok(ev.GetEventEnvelope())); } catch(Exception e) { response.Return(DreamMessage.BadRequest(e.Message)); } yield break; }
public void PubSub_end_to_end() { XUri testUri = _mockUri.At("foo", "sub1"); string serviceKey = "1234"; DreamCookie accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri); XDoc msg = new XDoc("foo"); DispatcherEvent ev = new DispatcherEvent( msg, new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); XDoc set = new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Add(accessCookie.AsSetCookieDocument) .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", testUri).End() .End(); // create subscription using a mockservice, so we get the general pubsub subscribe injected MockServiceInfo subMock = MockService.CreateMockService(_hostInfo); subMock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) { DreamMessage r = subMock.Service.PubSub.At("subscribers").PostAsync(set).Wait(); Assert.IsTrue(r.IsSuccessful); Assert.AreEqual(DreamStatus.Created, r.Status); response2.Return(DreamMessage.Ok()); }; subMock.AtLocalHost.Post(); // set up subscription destination mock DreamMessage receivedEvent = null; XUri recipient = null; ManualResetEvent resetEvent = new ManualResetEvent(false); MockPlug.Register(_mockUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response2) { _log.DebugFormat("destination called: {0}", uri); recipient = plug.Uri; receivedEvent = request; response2.Return(DreamMessage.Ok()); resetEvent.Set(); }); // publish event via a mock service, so we get the general pubsub publish injected MockServiceInfo pubMock = MockService.CreateMockService(_hostInfo); pubMock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) { DreamMessage r = pubMock.Service.PubSub.At("publish").PostAsync(ev.AsMessage()).Wait(); Assert.IsTrue(r.IsSuccessful); Assert.AreEqual(DreamStatus.Ok, r.Status); response2.Return(DreamMessage.Ok()); }; pubMock.AtLocalHost.Post(); // wait for async dispatch to happen if(!resetEvent.WaitOne(1000, false)) { Assert.Fail("async dispatch didn't happen"); } Assert.AreEqual(recipient, testUri); Assert.AreEqual(msg, receivedEvent.ToDocument()); Assert.AreEqual(serviceKey, DreamCookie.GetCookie(receivedEvent.Cookies, "service-key").Value); Assert.AreEqual(ev.Id, receivedEvent.Headers.DreamEventId); }
public void Chained_pubSub_end_to_end() { XUri testUri = _mockUri.At("foo", "sub1"); string serviceKey = "1234"; DreamCookie accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri); XDoc msg = new XDoc("foo"); DispatcherEvent ev = new DispatcherEvent( msg, new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); XDoc set = new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Add(accessCookie.AsSetCookieDocument) .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", testUri).End() .End(); // create pubsub chain Plug middle = Plug.New(_hostInfo.Host.LocalMachineUri.At("host", "$pubsub", "subscribers")); Plug upstreamPubSub = CreatePubSubService("upstream", new XDoc("config").Start("downstream").Elem("uri", middle).End()).WithInternalKey().AtLocalHost; Plug downstreamPubSub = CreatePubSubService("downstream", new XDoc("config").Start("upstream").Elem("uri", middle).End()).WithInternalKey().AtLocalHost; // create subscription DreamMessage response = downstreamPubSub.At("subscribers").PostAsync(set).Wait(); Assert.IsTrue(response.IsSuccessful); Assert.AreEqual(DreamStatus.Created, response.Status); // check that downstream really has the subscription Thread.Sleep(1000); DreamMessage downstreamSet = downstreamPubSub.At("subscribers").GetAsync().Wait(); Assert.IsTrue(downstreamSet.IsSuccessful); XDoc downstreamSetDoc = downstreamSet.ToDocument(); Assert.AreEqual(testUri.ToString(), downstreamSetDoc["subscription/recipient/uri"].AsText); // check that upstream really has the subscription DreamMessage upstreamSet = downstreamPubSub.At("subscribers").GetAsync().Wait(); Assert.IsTrue(upstreamSet.IsSuccessful); XDoc upstreamSetDoc = upstreamSet.ToDocument(); Assert.AreEqual(testUri.ToString(), upstreamSetDoc["subscription/recipient/uri"].AsText); // set up subscription destination mock DreamMessage receivedEvent = null; XDoc receivedDoc = null; XUri recipient = null; ManualResetEvent resetEvent = new ManualResetEvent(false); MockPlug.Register(_mockUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response2) { recipient = plug.Uri; receivedEvent = request; receivedDoc = request.ToDocument(); response2.Return(DreamMessage.Ok()); resetEvent.Set(); }); // publish event via a mock service, since publish is marked internal _log.DebugFormat("setting up Mock Service"); DreamCookie upstreamCookie = upstreamPubSub.CookieJar.Fetch(upstreamPubSub.Uri)[0]; XDoc upstreamSetCookieElement = upstreamCookie.AsSetCookieDocument; MockServiceInfo mock = MockService.CreateMockService( _hostInfo, new XDoc("config") .Elem("uri.pubsub", upstreamPubSub.Uri) .Add(upstreamSetCookieElement)); mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response2) { _log.DebugFormat("publishing event (in MockService)"); DreamMessage r = mock.Service.PubSub.At("publish").PostAsync(ev.AsMessage()).Wait(); Assert.IsTrue(r.IsSuccessful); Assert.AreEqual(DreamStatus.Ok, r.Status); response2.Return(DreamMessage.Ok()); }; _log.DebugFormat("publishing event via up Mock Service"); mock.AtLocalHost.Post(); // wait for async dispatch to happen _log.DebugFormat("waiting on async dispatch"); if(!resetEvent.WaitOne((int)TimeSpan.FromSeconds(1).TotalMilliseconds, true)) { Assert.Fail("async dispatch didn't happen"); } Assert.AreEqual(recipient, testUri); Assert.AreEqual(msg, receivedDoc); Assert.AreEqual(serviceKey, DreamCookie.GetCookie(receivedEvent.Cookies, "service-key").Value); Assert.AreEqual(ev.Id, receivedEvent.Headers.DreamEventId); }
protected override Yield FilterRecipients(DispatcherEvent ev, PubSubSubscription subscription, Result<DispatcherEvent> result) { var recipients2 = new List<DispatcherRecipient>(); uint? pageid = null; string wikiId = null; if(ev.HasDocument) { var changeDoc = ev.AsDocument(); pageid = changeDoc["pageid"].AsUInt; wikiId = changeDoc["@wikiid"].AsText; } var userIds = new Dictionary<int, DispatcherRecipient>(); foreach(var recipient in subscription.Recipients) { var authtoken = recipient.Doc["@authtoken"].AsText; if(string.IsNullOrEmpty(authtoken)) { // if the recipient has no authtoken, but has a userid, collect the Id so we can authorize it against the page int? userId = recipient.Doc["@userid"].AsInt; if(userId.HasValue) { userIds.Add(userId.Value, recipient); } } else if(authtoken == _authtoken) { // master authtoken means the recipient doesn't need page level authorization (such as lucene) recipients2.Add(recipient); } else if(!string.IsNullOrEmpty(wikiId)) { var key = authtoken + ":" + wikiId; if(!_validatedKeys.Contains(key)) { // no valid key found, need to check with API to validate XDoc settings = null; yield return _deki.At("site", "settings") .With("apikey", _authtoken) .WithHeader("X-Deki-Site", "id=" + wikiId) .Get(new Result<DreamMessage>()) .Set(x => settings = x.IsSuccessful ? x.ToDocument() : null); if(settings == null || !authtoken.EqualsInvariant(settings["security/api-key"].AsText)) { continue; } _validatedKeys.Add(key); } // instance authtoken means the recipient doesn't need page level authorization (such as lucene) recipients2.Add(recipient); } } if(userIds.Count > 0 && (ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete"))) { // check all userId's against the page to prune set to authorized users var users = new XDoc("users"); foreach(int userid in userIds.Keys) { users.Start("user").Attr("id", userid).End(); } if(pageid.HasValue) { Result<DreamMessage> userAuthResult; yield return userAuthResult = _deki.At("pages", pageid.Value.ToString(), "allowed") .With("permissions", "read,subscribe") .With("filterdisabled", true) .WithHeader("X-Deki-Site", "id=" + wikiId) .PostAsync(users); DreamMessage userAuth = userAuthResult.Value; if(userAuth.IsSuccessful) { int authorized = 0; foreach(XDoc userid in userAuth.ToDocument()["user/@id"]) { DispatcherRecipient recipient; if(!userIds.TryGetValue(userid.AsInt.GetValueOrDefault(), out recipient)) { continue; } authorized++; recipients2.Add(recipient); } if(authorized != userIds.Count) { _log.DebugFormat("requested auth on {0} users, received auth on {1} for page {2}", userIds.Count, authorized, pageid.Value); } } else { _log.WarnFormat("unable to retrieve user auth for page '{0}': {1}", pageid, userAuth.Status); } } } result.Return(recipients2.Count == 0 ? null : ev.WithRecipient(true, recipients2.ToArray())); yield break; }
private void Update_Helper() { lock(_subscriptionsByOwner) { PubSubSubscription[] allSubs = CalculateCombinedSubscriptions(); _combinedSetVersion++; _combinedSet = new PubSubSubscriptionSet(_owner, _combinedSetVersion, _serviceKeySetCookie, allSubs); } // this is outside the lock, since it may be an expensive operation and shouldn't block lookups DispatcherEvent ev = new DispatcherEvent(_combinedSet.AsDocument(), new XUri("pubsub:///set/update"), null, _owner); _log.DebugFormat("updated combined set, dispatching as id '{0}'", ev.Id); Dispatch(ev); if(CombinedSetUpdated != null) { CombinedSetUpdated(this, EventArgs.Empty); } }
private void DispatchFromQueue(DispatcherEvent dispatchEvent, Action completionCallback) { Coroutine.Invoke(Dispatch_Helper, dispatchEvent, new Result()).WhenDone( r => { completionCallback(); if(r.HasException) { _log.ErrorExceptionMethodCall(r.Exception, "AsyncDispatcher", "async queue processor encountered an error"); } else { _log.DebugFormat("finished dispatch of event '{0}'", dispatchEvent.Id); } }); }
public void Dispatch_based_on_channel_match_with_different_wikiid_patterns_but_same_proxy_destination() { DispatcherEvent ev = new DispatcherEvent( new XDoc("msg"), new XUri("event://sales.mindtouch.com/deki/comments/create"), new XUri("http://foobar.com/some/comment")); List<DreamMessage> dispatches = new List<DreamMessage>(); XUri testUri = new XUri("http://sales.mindtouch.com/").At(StringUtil.CreateAlphaNumericKey(4)); AutoResetEvent resetEvent = new AutoResetEvent(false); int dispatchCounter = 0; int expectedDispatches = 0; MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) { if(testUri == plug.Uri) { dispatches.Add(request); dispatchCounter++; // ReSharper disable AccessToModifiedClosure if(dispatchCounter >= expectedDispatches) { // ReSharper restore AccessToModifiedClosure resetEvent.Set(); } } response.Return(DreamMessage.Ok()); }); Plug owner = Plug.New("mock:///pubsub"); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie }); int expectedCombinedSetUpdates = 2; int combinedSetUpdates = 0; AutoResetEvent setResetEvent = new AutoResetEvent(false); dispatcher.CombinedSetUpdated += delegate { combinedSetUpdates++; _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates); if(combinedSetUpdates >= expectedCombinedSetUpdates) { setResetEvent.Set(); } }; dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "event://sales.mindtouch.com/deki/comments/create") .Elem("channel", "event://sales.mindtouch.com/deki/comments/update") .Elem("uri.proxy", testUri) .Start("recipient").Elem("uri", testUri.At("sub1")).End() .End()); dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner2") .Start("subscription") .Attr("id", "3") .Elem("channel", "event://*/deki/comments/create") .Elem("channel", "event://*/deki/comments/update") .Elem("uri.proxy", testUri) .Start("recipient").Elem("uri", testUri.At("sub2")).End() .End()); // combinedset updates happen asynchronously, so give'em a chance Assert.IsTrue(setResetEvent.WaitOne(10000, false)); expectedDispatches = 1; dispatcher.Dispatch(ev); // dispatch happens async on a worker thread Assert.IsTrue(resetEvent.WaitOne(10000, false)); Assert.AreEqual(1, dispatches.Count); Assert.AreEqual(ev.AsMessage().ToDocument(), dispatches[0].ToDocument()); Assert.AreEqual(ev.Id, dispatches[0].Headers.DreamEventId); MockPlug.Deregister(testUri); }
public void Deserialize_wrong_version_throws() { var msg = new XDoc("msg"); var channel = new XUri("channel://foo.com/bar"); var origin = new XUri("http://foo.com/baz"); var ev = new DispatcherEvent(msg, channel, origin); var item = new DispatchItem( new XUri("http://foo"), ev, "abc" ); var serializer = new DispatchItemSerializer(); var stream = serializer.ToStream(item); stream.WriteByte(5); stream.Position = 0; try { serializer.FromStream(stream); Assert.Fail("should have thrown"); } catch(InvalidDataException) { return; } }
public void Dispatch(DispatcherEvent ev) { throw new NotImplementedException(); }
public void Dispatch_based_on_recipients() { int workers; int io; ThreadPool.GetAvailableThreads(out workers, out io); _log.DebugFormat("threadpool threads: {0}/{1}", workers, io); string proxyRecipient1 = "mailto:///[email protected]"; string proxyRecipient2 = "mailto:///[email protected]"; XDoc msg = new XDoc("foo"); DispatcherEvent ev = new DispatcherEvent( msg, new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")) .WithRecipient(false, new DispatcherRecipient(new XUri(proxyRecipient1)), new DispatcherRecipient(new XUri("mailto:///[email protected]")), new DispatcherRecipient(new XUri(proxyRecipient2))); Dictionary<XUri, DreamMessage> dispatches = new Dictionary<XUri, DreamMessage>(); XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4)); AutoResetEvent resetEvent = new AutoResetEvent(false); MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) { dispatches.Add(plug.Uri, request); resetEvent.Set(); response.Return(DreamMessage.Ok()); }); Plug owner = Plug.New("mock:///pubsub"); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie }); AutoResetEvent setResetEvent = new AutoResetEvent(false); dispatcher.CombinedSetUpdated += delegate { _log.DebugFormat("set updated"); setResetEvent.Set(); }; XUri proxy = testUri.At("proxy"); _log.DebugFormat("registering set"); dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Elem("uri.proxy", proxy) .Start("recipient").Elem("uri", proxyRecipient1).End() .Start("recipient").Elem("uri", proxyRecipient2).End() .End() .Start("subscription") .Attr("id", "2") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", testUri.At("sub2")).End() .End()); //Set updates happen asynchronously, so give it a chance _log.DebugFormat("giving registration a chance to manifest"); Assert.IsTrue(setResetEvent.WaitOne(10000, false)); _log.DebugFormat("dispatching event"); dispatcher.Dispatch(ev); // dispatch happens async on a worker thread Assert.IsTrue(resetEvent.WaitOne(1000, false)); Thread.Sleep(200); Assert.AreEqual(1, dispatches.Count); Assert.IsTrue(dispatches.ContainsKey(proxy)); Assert.AreEqual(msg, dispatches[proxy].ToDocument()); Assert.AreEqual(ev.Id, dispatches[proxy].Headers.DreamEventId); string[] recipients = dispatches[proxy].Headers.DreamEventRecipients; Assert.AreEqual(2, recipients.Length); Assert.Contains(proxyRecipient1, recipients); Assert.Contains(proxyRecipient2, recipients); MockPlug.Deregister(testUri); }
public void Speed() { var body = new XDoc("msg").Elem("foo", "bar"); var channel = new XUri("channel://foo.com/bar"); var resource = new XUri("http://foo.com/baz/0"); var origin1 = new XUri("http://foo.com/baz/1"); var origin2 = new XUri("http://foo.com/baz/2"); var recipient1 = new DispatcherRecipient(new XUri("http://recipient1")); var recipient2 = new DispatcherRecipient(new XUri("http://recipient2")); var via1 = new XUri("http://via1"); var via2 = new XUri("http://via2"); var ev = new DispatcherEvent(body, channel, resource, origin1, origin2); ev = ev.WithRecipient(false, recipient1, recipient2).WithVia(via1).WithVia(via2); var item = new DispatchItem( new XUri("http://foo"), ev, "abc" ); var serializer = new DispatchItemSerializer(); Stream stream = null; var n = 100000; var t = Stopwatch.StartNew(); for(var i = 0; i < n; i++) { stream = serializer.ToStream(item); } t.Stop(); Console.WriteLine("serialize {0:0} items/sec", n / t.Elapsed.TotalSeconds); t = Stopwatch.StartNew(); for(var i = 0; i < n; i++) { serializer.FromStream(stream); stream.Seek(0, SeekOrigin.Begin); } t.Stop(); Console.WriteLine("deserialize {0:0} items/sec", n / t.Elapsed.TotalSeconds); }
/// <summary> /// Override hook for modifying the selection of event listeners based on channel and resource matches. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result<Dictionary<XUri, List<DispatcherRecipient>>> result) { // dispatch to all subscriptions that listen for this event and its contents _log.Debug("trying dispatch based on channel matches"); ICollection<PubSubSubscription> listeningSubs = null; lock(_channelMap) { if(ev.Resource != null) { listeningSubs = _resourceMap.GetMatches(ev.Resource); } listeningSubs = _channelMap.GetMatches(ev.Channel, listeningSubs); } Dictionary<XUri, List<DispatcherRecipient>> listeners = new Dictionary<XUri, List<DispatcherRecipient>>(); foreach(PubSubSubscription sub in listeningSubs) { List<DispatcherRecipient> recipients; if(!listeners.TryGetValue(sub.Destination, out recipients)) { recipients = new List<DispatcherRecipient>(); listeners.Add(sub.Destination, recipients); } foreach(DispatcherRecipient recipient in sub.Recipients) { if(!recipients.Contains(recipient)) { recipients.Add(recipient); } } } result.Return(listeners); yield break; }
public void Dispatch_based_on_channel_and_resource_match() { DispatcherEvent ev = new DispatcherEvent( new XDoc("msg"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); Dictionary<XUri, DreamMessage> dispatches = new Dictionary<XUri, DreamMessage>(); XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4)); AutoResetEvent resetEvent = new AutoResetEvent(false); int expectedDispatches = 0; MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) { dispatches.Add(plug.Uri, request); // ReSharper disable AccessToModifiedClosure if(dispatches.Count >= expectedDispatches) { // ReSharper restore AccessToModifiedClosure resetEvent.Set(); } response.Return(DreamMessage.Ok()); }); Plug owner = Plug.New("mock:///pubsub"); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie }); int expectedCombinedSetUpdates = 2; int combinedSetUpdates = 0; AutoResetEvent setResetEvent = new AutoResetEvent(false); dispatcher.CombinedSetUpdated += delegate { combinedSetUpdates++; _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates); if(combinedSetUpdates >= expectedCombinedSetUpdates) { setResetEvent.Set(); } }; dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Elem("uri.resource", "http://*/some/*") .Start("recipient").Elem("uri", testUri.At("sub1")).End() .End() .Start("subscription") .Attr("id", "2") .Elem("channel", "channel:///foo/baz") .Elem("uri.resource", "http://*/some/*") .Start("recipient").Elem("uri", testUri.At("sub2")).End() .End()); dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner2") .Start("subscription") .Attr("id", "3") .Elem("channel", "channel:///foo/bar") .Elem("uri.resource", "http://foobar.com/some/page") .Start("recipient").Elem("uri", testUri.At("sub3")).End() .End() .Start("subscription") .Attr("id", "4") .Elem("channel", "channel:///foo/bar") .Elem("uri.resource", "http://baz.com/some/*") .Start("recipient").Elem("uri", testUri.At("sub4")).End() .End()); // combinedset updates happen asynchronously, so give'em a chance Assert.IsTrue(setResetEvent.WaitOne(10000, false)); expectedDispatches = 2; dispatcher.Dispatch(ev); // dispatch happens async on a worker thread Assert.IsTrue(resetEvent.WaitOne(10000, false)); Thread.Sleep(200); Assert.AreEqual(2, dispatches.Count); Assert.IsTrue(dispatches.ContainsKey(testUri.At("sub1"))); Assert.AreEqual(ev.AsMessage().ToDocument(), dispatches[testUri.At("sub1")].ToDocument()); Assert.AreEqual(ev.Id, dispatches[testUri.At("sub1")].Headers.DreamEventId); Assert.IsTrue(dispatches.ContainsKey(testUri.At("sub3"))); MockPlug.Deregister(testUri); }
/// <summary> /// Override hook for modifying the selection of event listeners based on recipients subscribed to the event. /// </summary> /// <param name="ev">Event to be dispatched.</param> /// <param name="result">The <see cref="Result"/>instance to be returned by this method.</param> /// <returns>Synchronization handle for the action's execution.</returns> protected virtual Yield GetListenersForRecipients(DispatcherEvent ev, Result<Dictionary<XUri, List<DispatcherRecipient>>> result) { //if the event has recipients attached, do subscription lookup by recipients _log.Debug("trying dispatch based on event recipient list event"); lock(_destinationsByRecipient) { Dictionary<XUri, List<DispatcherRecipient>> listeners = new Dictionary<XUri, List<DispatcherRecipient>>(); foreach(DispatcherRecipient recipient in ev.Recipients) { List<XUri> destinations; if(_destinationsByRecipient.TryGetValue(recipient, out destinations)) { foreach(XUri destination in destinations) { List<DispatcherRecipient> recipients; if(!listeners.TryGetValue(destination, out recipients)) { recipients = new List<DispatcherRecipient>(); listeners.Add(destination, recipients); } if(!recipients.Contains(recipient)) { recipients.Add(recipient); } } } } result.Return(listeners); } yield break; }
public void Repeated_dispatch_failure_kicks_subscription_set() { // TODO (steveb): test fails under Mono 2.8.2 var sub1Uri = new XUri("http://sub1/foo"); var sub1Mock = MockPlug.Register(sub1Uri); sub1Mock.Expect().Verb("POST").Response(DreamMessage.BadRequest("nobody home")); var sub2Uri = new XUri("http://sub2/foo"); var sub2Mock = MockPlug.Register(sub2Uri); sub2Mock.Expect().Verb("POST").Response(DreamMessage.BadRequest("nobody home")); var ev = new DispatcherEvent( new XDoc("msg"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); var cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); var dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = Plug.New("mock:///pubsub"), ServiceAccessCookie = cookie }); var expectedCombinedSetUpdates = 2; var combinedSetUpdates = 0; var setResetEvent = new ManualResetEvent(false); dispatcher.CombinedSetUpdated += delegate { combinedSetUpdates++; _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates); if(combinedSetUpdates >= expectedCombinedSetUpdates) { setResetEvent.Set(); } }; var location1 = dispatcher.RegisterSet(new XDoc("subscription-set") .Attr("max-failures", 0) .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", sub1Uri).End() .End()).Item1.Location; var location2 = dispatcher.RegisterSet(new XDoc("subscription-set") .Attr("max-failures", 0) .Elem("uri.owner", "http:///owner2") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", sub2Uri).End() .End()).Item1.Location; Assert.IsTrue(setResetEvent.WaitOne(10000, false), "combined set didn't change expected number of times"); Assert.IsNotNull(dispatcher[location1]); Assert.IsNotNull(dispatcher[location2]); dispatcher.Dispatch(ev); Assert.IsTrue(sub1Mock.WaitAndVerify(TimeSpan.FromSeconds(10)), sub1Mock.VerificationFailure); Assert.IsTrue(sub2Mock.WaitAndVerify(TimeSpan.FromSeconds(10)), sub1Mock.VerificationFailure); Assert.IsTrue(Wait.For(() => dispatcher[location2] == null, TimeSpan.FromSeconds(10)), "Second set wasn't kicked"); Assert.IsTrue(Wait.For(() => dispatcher[location1] == null, TimeSpan.FromSeconds(10)), "First set wasn't kicked"); }
private Yield Dispatch_Helper(DispatcherEvent dispatchEvent, Result result) { Dictionary<XUri, List<DispatcherRecipient>> listeningEndpoints = null; if(dispatchEvent.Recipients != null && dispatchEvent.Recipients.Length > 0) { yield return Coroutine.Invoke(GetListenersForRecipients, dispatchEvent, new Result<Dictionary<XUri, List<DispatcherRecipient>>>()).Set(v => listeningEndpoints = v); } else { yield return Coroutine.Invoke(GetListenersByChannelResourceMatch, dispatchEvent, new Result<Dictionary<XUri, List<DispatcherRecipient>>>()).Set(v => listeningEndpoints = v); } if(listeningEndpoints.Count == 0) { _log.DebugFormat("event '{0}' for resource '{1}' has no endpoints to dispatch to", dispatchEvent.Id, dispatchEvent.Resource); } else { _log.DebugFormat("event '{0}' for resource '{1}' ready for dispatch with {2} endpoint(s)", dispatchEvent.Id, dispatchEvent.Resource, listeningEndpoints.Count); } foreach(KeyValuePair<XUri, List<DispatcherRecipient>> destination in listeningEndpoints) { var uri = destination.Key; DispatcherEvent subEvent = null; yield return Coroutine.Invoke(DetermineRecipients, dispatchEvent, destination.Value.ToArray(), new Result<DispatcherEvent>()).Set(v => subEvent = v); if(subEvent == null) { _log.DebugFormat("no recipient union for event '{0}' and {1}", dispatchEvent.Id, uri); continue; } _log.DebugFormat("dispatching event '{0}' to {1}", subEvent.Id, uri); Plug p = Plug.New(uri); p = p.WithCookieJar(_cookieJar); Result<DreamMessage> response = p.Post(subEvent.AsMessage(), new Result<DreamMessage>(TimeSpan.MaxValue)); response.WhenDone(r => DispatchCompletion_Helper(uri, r)); } result.Return(); yield break; }
public void New_Event_from_XDoc_and_back() { XDoc msg = new XDoc("msg"); XUri channel = new XUri("channel://foo.com/bar"); XUri origin = new XUri("http://foo.com/baz"); DispatcherEvent ev = new DispatcherEvent(msg, channel, origin); Assert.IsFalse(string.IsNullOrEmpty(ev.Id)); Assert.AreEqual(channel, ev.Channel); List<XUri> origins = new List<XUri>(ev.Origins); Assert.AreEqual(1, origins.Count); Assert.AreEqual(origin, origins[0]); DreamMessage message = ev.AsMessage(); Assert.AreEqual(msg, message.ToDocument()); Assert.AreEqual(channel.ToString(), message.Headers.DreamEventChannel); Assert.AreEqual(origin.ToString(), message.Headers.DreamEventOrigin[0]); }
protected override Yield DetermineRecipients(DispatcherEvent ev, DispatcherRecipient[] recipients, Result<DispatcherEvent> result) { List<DispatcherRecipient> recipients2 = new List<DispatcherRecipient>(); Dictionary<int, DispatcherRecipient> userIds = new Dictionary<int, DispatcherRecipient>(); foreach(DispatcherRecipient r in recipients) { string authtoken = r.Doc["@authtoken"].AsText; if(string.IsNullOrEmpty(authtoken)) { // if the reciepient has no authtoken, but has a userid, collect the Id so we can authorize it against the page int? userId = r.Doc["@userid"].AsInt; if(userId.HasValue) { userIds.Add(userId.Value, r); } } else if(authtoken == _authtoken) { // authtoken means the recipient doesn't need page level authorization (such as lucene) recipients2.Add(r); } } if(userIds.Count > 0 && (ev.Channel.Segments.Length <= 2 || !StringUtil.EqualsInvariantIgnoreCase(ev.Channel.Segments[2], "delete"))) { // check all userId's against the page to prune set to authorized users XDoc users = new XDoc("users"); foreach(int userid in userIds.Keys) { users.Start("user").Attr("id", userid).End(); } XDoc changeDoc = ev.AsDocument(); uint? pageid = changeDoc["pageid"].AsUInt; string wikiId = changeDoc["@wikiid"].AsText; if(pageid.HasValue) { Result<DreamMessage> userAuthResult; yield return userAuthResult = _deki.At("pages", pageid.Value.ToString(), "allowed") .With("permissions", "read,subscribe") .With("filterdisabled", true) .WithHeader("X-Deki-Site", "id=" + wikiId) .PostAsync(users); DreamMessage userAuth = userAuthResult.Value; if(userAuth.IsSuccessful) { int authorized = 0; foreach(XDoc userid in userAuth.ToDocument()["user/@id"]) { DispatcherRecipient recipient; if(!userIds.TryGetValue(userid.AsInt.GetValueOrDefault(), out recipient)) { continue; } authorized++; recipients2.Add(recipient); } if(authorized != userIds.Count) { _log.DebugFormat("requested auth on {0} users, received auth on {1} for page {2}", userIds.Count, authorized, pageid.Value); } } else { _log.WarnFormat("unable to retrieve user auth for page '{0}': {1}", pageid, userAuth.Status); } } } result.Return(recipients2.Count == 0 ? null : ev.WithRecipient(true, recipients2.ToArray())); yield break; }
public void New_Event_from_bytes_and_back_as_multiple_streams() { byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; DispatcherEvent ev = new DispatcherEvent(DreamMessage.Ok(MimeType.BINARY, bytes), new XUri("channel:///foo"), new XUri("http:///origin")); DreamMessage m1 = ev.AsMessage(); DreamMessage m2 = ev.AsMessage(); Assert.AreEqual(9, m1.ContentLength); Assert.AreEqual(9, m2.ContentLength); MemoryStream ms1 = m1.ToStream().ToMemoryStream(m1.ContentLength, new Result<MemoryStream>()).Wait(); MemoryStream ms2 = m1.ToStream().ToMemoryStream(m1.ContentLength, new Result<MemoryStream>()).Wait(); Assert.AreEqual(bytes, ms1.GetBuffer()); Assert.AreEqual(bytes, ms2.GetBuffer()); }
protected override Yield GetListenersByChannelResourceMatch(DispatcherEvent ev, Result<Dictionary<XUri, List<PubSubSubscription>>> result) { if(!ev.Channel.Segments[1].EqualsInvariantIgnoreCase("pages")) { // not a page DispatcherEvent or a page delete DispatcherEvent, use default matcher Result<Dictionary<XUri, List<PubSubSubscription>>> baseResult; yield return baseResult = Coroutine.Invoke(base.GetListenersByChannelResourceMatch, ev, new Result<Dictionary<XUri, List<PubSubSubscription>>>()); result.Return(baseResult); yield break; } var matches = new List<PubSubSubscription>(); if(ev.Channel.Segments.Length <= 2 || !ev.Channel.Segments[2].EqualsInvariantIgnoreCase("delete")) { // dispatch to all PubSubSubscriptions that listen for this DispatcherEvent and its contents XDoc evDoc = ev.AsDocument(); uint? pageid = evDoc["pageid"].AsUInt; string wikiId = evDoc["@wikiid"].AsText; bool first = true; _log.DebugFormat("trying dispatch based on channel & page PubSubSubscriptions for page '{0}' from wiki '{1}'", pageid, wikiId); // fetch parent page id's for this page so that we can resolve infinite depth PubSubSubscriptions Result<DreamMessage> pageHierarchyResult; yield return pageHierarchyResult = _deki.At("pages", pageid.ToString()).WithHeader("X-Deki-Site", "id=" + wikiId).GetAsync(); DreamMessage pageHierarchy = pageHierarchyResult.Value; if(pageHierarchy.IsSuccessful) { XDoc pageDoc = pageHierarchy.ToDocument(); while(pageid.HasValue) { List<Tuplet<PubSubSubscription, bool>> subs; _subscriptionsByPage.TryGetValue(pageid.Value, out subs); if(subs != null) { // only the first pageId (the one from the event) triggers on non-infinite depth subs foreach(var sub in subs) { if((sub.Item2 || first) && !matches.Contains(sub.Item1)) { matches.Add(sub.Item1); } } } // get parent id and then set pageDoc to the parent's subdoc, so we can descend the ancesstor tree further pageid = pageDoc["page.parent/@id"].AsUInt; pageDoc = pageDoc["page.parent"]; first = false; } } else { _log.WarnFormat("unable to retrieve page doc for page '{0}': {1}", pageid, pageHierarchy.Status); } } ICollection<PubSubSubscription> listeningSubs; lock(_channelMap) { // get all the PubSubSubscriptions that are wild card matches (which is basically those that didn't // have any resources in their PubSubSubscription) and add them to the above matches foreach(var sub in _resourceMap.GetMatches(new XUri("http://dummy/dummy"))) { if(!matches.Contains(sub)) { matches.Add(sub); } } listeningSubs = _channelMap.GetMatches(ev.Channel, matches); } var listeners = new Dictionary<XUri, List<PubSubSubscription>>(); foreach(var sub in listeningSubs) { List<PubSubSubscription> subs; if(!listeners.TryGetValue(sub.Destination, out subs)) { subs = new List<PubSubSubscription>(); listeners.Add(sub.Destination, subs); subs.Add(sub); } else if(!subs.Contains(sub)) { subs.Add(sub); } } result.Return(listeners); yield break; }
public void Failed_dispatch_followed_by_success_should_reset_fail_count() { bool fail = true; DispatcherEvent ev = new DispatcherEvent( new XDoc("msg"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4)); AutoResetEvent resetEvent = new AutoResetEvent(false); int mockCalled = 0; MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) { mockCalled++; // ReSharper disable AccessToModifiedClosure _log.DebugFormat("mock called {0} times (fail={1}): {2}", mockCalled, fail, uri); // ReSharper restore AccessToModifiedClosure resetEvent.Set(); // ReSharper disable AccessToModifiedClosure response.Return(fail ? DreamMessage.InternalError() : DreamMessage.Ok()); // ReSharper restore AccessToModifiedClosure }); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = Plug.New("mock:///pubsub"), ServiceAccessCookie = cookie }); int expectedCombinedSetUpdates = 1; int combinedSetUpdates = 0; AutoResetEvent setResetEvent = new AutoResetEvent(false); dispatcher.CombinedSetUpdated += delegate { combinedSetUpdates++; _log.DebugFormat("combinedset updated ({0})", combinedSetUpdates); if(combinedSetUpdates >= expectedCombinedSetUpdates) { setResetEvent.Set(); } }; string location = dispatcher.RegisterSet( new XDoc("subscription-set") .Attr("max-failures", 1) .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", testUri.At("foo")).End() .End()).Item1.Location; Assert.IsTrue(setResetEvent.WaitOne(10000, false)); Assert.IsNotNull(dispatcher[location]); _log.DebugFormat("first dispatch (fail={0})", fail); dispatcher.Dispatch(ev); Assert.IsTrue(resetEvent.WaitOne(10000, false)); Thread.Sleep(1000); // failure gets dealt with async Assert.IsNotNull(dispatcher[location]); fail = false; _log.DebugFormat("second dispatch (fail={0})", fail); dispatcher.Dispatch(ev); Assert.IsTrue(resetEvent.WaitOne(10000, false)); Thread.Sleep(1000); // failure reset gets dealt with async Assert.IsNotNull(dispatcher[location]); fail = true; _log.DebugFormat("third dispatch (fail={0})", fail); dispatcher.Dispatch(ev); Assert.IsTrue(resetEvent.WaitOne(10000, false)); Thread.Sleep(1000); // failure gets dealt with async Assert.IsNotNull(dispatcher[location]); _log.DebugFormat("fourth dispatch (fail={0})", fail); dispatcher.Dispatch(ev); Assert.IsTrue(resetEvent.WaitOne(10000, false)); Thread.Sleep(1000); // failure gets dealt with async Assert.IsNull(dispatcher[location]); MockPlug.Deregister(testUri); }
public void Parallel_chaining_subscription_and_message_propagation() { var rootPubsub = Plug.New(_hostInfo.Host.LocalMachineUri.At("host", "$pubsub", "subscribers")); var goTrigger = new ManualResetEvent(false); var pubsubResults = new List<Result<Plug>>(); for(var i = 0; i < 10; i++) { pubsubResults.Add(Async.ForkThread(() => { goTrigger.WaitOne(); return CreatePubSubService("upstream", new XDoc("config").Start("downstream").Elem("uri", rootPubsub).End()).WithInternalKey().AtLocalHost; }, new Result<Plug>())); } var subscriberResults = new List<Result<Tuplet<XUri, AutoMockPlug>>>(); for(var i = 0; i < 20; i++) { var mockUri = new XUri("http://mock/" + i); subscriberResults.Add(Async.ForkThread(() => { goTrigger.WaitOne(); rootPubsub.With("apikey", _hostInfo.ApiKey).Post(new XDoc("subscription-set") .Elem("uri.owner", mockUri) .Start("subscription") .Attr("id", "1") .Elem("channel", "channel://foo/bar") .Start("recipient").Elem("uri", mockUri).End() .End()); var mock = MockPlug.Register(mockUri); return new Tuplet<XUri, AutoMockPlug>(mockUri, mock); }, new Result<Tuplet<XUri, AutoMockPlug>>())); } goTrigger.Set(); var pubsubs = new List<Plug>(); foreach(var r in pubsubResults) { pubsubs.Add(r.Wait()); } var endpoints = new List<XUri>(); var mocks = new List<AutoMockPlug>(); foreach(var r in subscriberResults) { var v = r.Wait(); endpoints.Add(v.Item1); mocks.Add(v.Item2); } foreach(var pubsub in pubsubs) { Plug plug = pubsub; Wait.For(() => { var set = plug.At("subscribers").Get(); return set.ToDocument()["subscription/recipient"].ListLength == endpoints.Count; }, TimeSpan.FromSeconds(10)); } var ev = new DispatcherEvent(new XDoc("blah"), new XUri("channel://foo/bar"), new XUri("http://foobar.com/some/page")); foreach(var mock in mocks) { mock.Expect().Verb("POST").RequestDocument(ev.AsDocument()).Response(DreamMessage.Ok()); } pubsubs[0].At("publish").Post(ev.AsMessage()); foreach(var mock in mocks) { Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure); } }
/// <summary> /// Dispatch an event against the registered subscriptions. /// </summary> /// <param name="ev">Dispatch event instance.</param> public void Dispatch(DispatcherEvent ev) { if(ev.HasVisited(_owner)) { // this event is in a dispatch loop, so we drop it if(_log.IsWarnEnabled) { _log.WarnFormat("event for channel '{0}' already visited the service, dropping", ev.Channel); if(_log.IsDebugEnabled) { _log.Debug(" event origin:"); foreach(XUri origin in ev.Origins) { _log.DebugFormat(" - {0}", origin); } _log.Debug(" event route:"); foreach(XUri via in ev.Via) { _log.DebugFormat(" - {0}", via); } } } throw new DreamBadRequestException("Dispatch loop detected: The event has already been dispatched by this service"); } if(_log.IsDebugEnabled) { _log.DebugFormat("Dispatcher '{0}' dispatching '{1}' on channel '{2}' with resource '{3}'", _owner, ev.Id, ev.Channel, ev.Resource); } DispatcherEvent dispatchEvent = ev.WithVia(_owner); if(!_dispatchQueue.TryEnqueue(dispatchEvent)) { throw new InvalidOperationException(string.Format("Enqueue of '{0}' failed.", dispatchEvent.Id)); } }
public void Can_aggregate_from_multiple_dream_hosts() { // set up hosts _log.DebugFormat("---- creating upstream hosts"); var sourceHost1 = DreamTestHelper.CreateRandomPortHost(); var source1PubSub = Plug.New(sourceHost1.LocalHost.At("host", "$pubsub").With("apikey", sourceHost1.ApiKey)); var sourceHost2 = DreamTestHelper.CreateRandomPortHost(); var source2PubSub = Plug.New(sourceHost2.LocalHost.At("host", "$pubsub").With("apikey", sourceHost2.ApiKey)); // create aggregator _log.DebugFormat("---- creating downstream host"); var aggregatorPath = "pubsubaggregator"; var aggregatorHost = DreamTestHelper.CreateRandomPortHost(); aggregatorHost.Host.RunScripts(new XDoc("config") .Start("script").Start("action") .Attr("verb", "POST") .Attr("path", "/host/services") .Start("config") .Elem("path", aggregatorPath) .Elem("sid", "sid://mindtouch.com/dream/2008/10/pubsub") .Elem("apikey", "abc") .Start("upstream") .Elem("uri", source1PubSub.At("subscribers")) .Elem("uri", source2PubSub.At("subscribers")) .End() .End().End(), null); var aggregatorPubSub = aggregatorHost.LocalHost.At(aggregatorPath).With("apikey", "abc"); // create subscription _log.DebugFormat("---- create downstream subscription"); var testUri = new XUri("http://mock/aggregator"); var serviceKey = "1234"; var accessCookie = DreamCookie.NewSetCookie("service-key", serviceKey, testUri); var subscriberApiKey = "xyz"; var set = new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Add(accessCookie.AsSetCookieDocument) .Elem("channel", "channel:///foo/*") .Start("recipient") .Attr("authtoken", subscriberApiKey) .Elem("uri", testUri) .End() .End(); var r = aggregatorPubSub.At("subscribers").PostAsync(set).Wait(); Assert.IsTrue(r.IsSuccessful, r.Status.ToString()); Assert.AreEqual(DreamStatus.Created, r.Status); // Verify that upstream host pubsub services have the subscription Func<DreamMessage, bool> waitFunc = response => { var sub = response.ToDocument()["subscription-set/subscription[channel='channel:///foo/*']"]; return (!sub.IsEmpty && sub["recipient/uri"].AsText.EqualsInvariantIgnoreCase(testUri.ToString()) && sub["recipient/@authtoken"].AsText.EqualsInvariant(subscriberApiKey)); }; Assert.IsTrue(WaitFor(source1PubSub.At("diagnostics", "subscriptions"), waitFunc, TimeSpan.FromSeconds(5)), "source 1 didn't get the subscription"); Assert.IsTrue(WaitFor(source2PubSub.At("diagnostics", "subscriptions"), waitFunc, TimeSpan.FromSeconds(5)), "source 2 didn't get the subscription"); // set up destination mock DispatcherEvent aggregatorEvent = new DispatcherEvent( new XDoc("aggregator"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); DispatcherEvent source1Event = new DispatcherEvent( new XDoc("source1"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); DispatcherEvent source2Event = new DispatcherEvent( new XDoc("source2"), new XUri("channel:///foo/bar"), new XUri("http://foobar.com/some/page")); var mock = MockPlug.Register(testUri); // Publish event into aggregator mock.Expect().Verb("POST").RequestDocument(aggregatorEvent.AsDocument()); r = aggregatorPubSub.At("publish").PostAsync(aggregatorEvent.AsMessage()).Wait(); Assert.IsTrue(r.IsSuccessful, r.Status.ToString()); Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure); // Publish event into source1 mock.Reset(); mock.Expect().Verb("POST").RequestDocument(source1Event.AsDocument()); r = source1PubSub.At("publish").PostAsync(source1Event.AsMessage()).Wait(); Assert.IsTrue(r.IsSuccessful, r.Status.ToString()); Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure); // Publish event into source2 mock.Reset(); mock.Expect().Verb("POST").RequestDocument(source2Event.AsDocument()); r = source2PubSub.At("publish").PostAsync(source2Event.AsMessage()).Wait(); Assert.IsTrue(r.IsSuccessful, r.Status.ToString()); Assert.IsTrue(mock.WaitAndVerify(TimeSpan.FromSeconds(10)), mock.VerificationFailure); }
public void Dispatch_will_send_https_resources_to_subscriptions_without_resource() { DispatcherEvent ev = new DispatcherEvent( new XDoc("msg"), new XUri("channel:///foo/bar"), new XUri("https://foobar.com/some/page")); XUri testUri = new XUri("http:///").At(StringUtil.CreateAlphaNumericKey(4)); AutoResetEvent resetEvent = new AutoResetEvent(false); MockPlug.Register(testUri, delegate(Plug plug, string verb, XUri uri, DreamMessage request, Result<DreamMessage> response) { resetEvent.Set(); response.Return(DreamMessage.Ok()); }); Plug owner = Plug.New("mock:///pubsub"); DreamCookie cookie = DreamCookie.NewSetCookie("foo", "bar", new XUri("http://xyz/abc/")); Dispatcher dispatcher = new Dispatcher(new DispatcherConfig { ServiceUri = owner, ServiceAccessCookie = cookie }); AutoResetEvent setResetEvent = new AutoResetEvent(false); dispatcher.CombinedSetUpdated += delegate { setResetEvent.Set(); }; dispatcher.RegisterSet( new XDoc("subscription-set") .Elem("uri.owner", "http:///owner1") .Start("subscription") .Attr("id", "1") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", testUri).End() .End()); // combinedset updates happen asynchronously, so give'em a chance Assert.IsTrue(setResetEvent.WaitOne(10000, false)); dispatcher.Dispatch(ev); // dispatch happens async on a worker thread Assert.IsTrue(resetEvent.WaitOne(10000, false)); MockPlug.Deregister(testUri); }