public static string GetUrlLocalUri(XUri confBaseUri, string url, bool includeQuery, bool decode) { if(string.IsNullOrEmpty(url)) { return null; } if(url.StartsWithInvariantIgnoreCase(confBaseUri.ToString())) { //Remove the wiki path prefix (everything before display generally) url = confBaseUri.SchemeHostPort + url.Substring(confBaseUri.ToString().Length); } XUri uri = XUri.TryParse(url); if(uri == null) { return null; } string ret = uri.Path; if(decode) { ret = XUri.Decode(ret); } if(includeQuery && !string.IsNullOrEmpty(uri.QueryFragment)) { ret += uri.QueryFragment; } return ret; }
// --- Constructors --- public RemoteInstanceManager(DekiWikiService dekiService, XUri directoryUri) : base(dekiService) { _directory = Plug.New(directoryUri); DreamMessage testMsg = _directory.GetAsync().Wait(); if (!testMsg.IsSuccessful) throw new DreamInternalErrorException(string.Format("Error validating remote deki portal service at '{0}'", directoryUri.ToString())); }
private string CachedWebGet(XUri uri, double? ttl) { string id = uri.ToString(); // fetch message from cache or from the web CacheEntry result; bool isNew = true; lock(_cacheLookup) { _cacheLookup.TryGetValue(id, out result); } // check if we have a cached entry if(result != null) { _log.DebugFormat("cache hit for '{0}'", result.Id); isNew = false; result.ResetMemoryExpiration(); if(result.Cache != null) { _log.DebugFormat("cache data in memory '{0}'", result.Id); return result.Cache; } // we have the result on disk, so let's fetch it DreamMessage msg = Storage.At(CACHE_DATA, result.Guid + ".bin").GetAsync().Wait(); if(msg.IsSuccessful) { _log.DebugFormat("cache data pulled from disk"); result.Cache = Encoding.UTF8.GetString(msg.AsBytes()); return result.Cache; } _log.DebugFormat("unable to fetch cache data from disk: {0}", msg.Status); } else { _log.DebugFormat("new cache item for '{0}'", id); result = new CacheEntry(id, ttl); } // do the web request Result<DreamMessage> response = new Result<DreamMessage>(); Plug.New(uri).WithTimeout(DEFAULT_WEB_TIMEOUT).InvokeEx("GET", DreamMessage.Ok(), response); DreamMessage message = response.Wait(); try { // check message status if(!message.IsSuccessful) { return message.Status == DreamStatus.UnableToConnect ? string.Format("(unable to fetch text document from uri [status: {0} ({1}), message: \"{2}\"])", (int)message.Status, message.Status, message.ToDocument()["message"].AsText) : string.Format("(unable to fetch text document from uri [status: {0} ({1})])", (int)message.Status, message.Status); } // check message size Result resMemorize = message.Memorize(_insertTextLimit, new Result()).Block(); if(resMemorize.HasException) { return "(text document is too large)"; } // check if response is an XML document result.Cache = message.AsText() ?? string.Empty; } finally { message.Close(); } // start timer to clean-up cached result if(result.Cache != null) { XDoc infoDoc = new XDoc("cache-entry") .Elem("guid", result.Guid) .Elem("id", result.Id) .Elem("expires", result.Expires); lock(result) { Storage.At(CACHE_DATA, result.Guid + ".bin").PutAsync(new DreamMessage(DreamStatus.Ok, null, MimeType.BINARY, Encoding.UTF8.GetBytes(result.Cache))).Wait(); Storage.At(CACHE_INFO, result.Guid + ".xml").PutAsync(infoDoc).Wait(); } if(isNew) { lock(_cacheLookup) { _cacheLookup[id] = result; } // this timer removes the cache entry from disk SetupCacheTimer(result); } } return result.Cache; }
public Yield UserLogin(DreamContext context, DreamMessage request, Result<DreamMessage> response) { string userSuppliedIdentifier = context.GetParam("url", null); if (String.IsNullOrEmpty(userSuppliedIdentifier)) { _log.Info("No identifier was specified"); throw new DreamBadRequestException("No identifier was specified."); } XUri returnUri = new XUri(context.GetParam("returnurl", null)); String realm = context.GetParam("realm", null); if (String.IsNullOrEmpty(realm)) { realm = returnUri.WithoutPathQueryFragment().ToString(); } IAuthenticationRequest openIdRequest; // dummy parameters required by DotNetOpenId 2.x; in 3.x, you can // just pass null to the OpenIdRelyingParty constructor. Uri identifierUri = new Uri(userSuppliedIdentifier); NameValueCollection queryCol = System.Web.HttpUtility.ParseQueryString(identifierUri.Query); OpenIdRelyingParty openid = new OpenIdRelyingParty(null, identifierUri, queryCol); // creating an OpenID request will authenticate that // the endpoint exists and is an OpenID provider. _log.DebugFormat("Creating OpenID request: identifier {0}, return URL {1}, realm {2}", userSuppliedIdentifier, returnUri.ToString(), realm); try { openIdRequest = openid.CreateRequest( userSuppliedIdentifier, realm, returnUri.ToUri()); } catch (OpenIdException ex) { _log.WarnFormat("'{0}' rejected as OpenID identifier: {1}", userSuppliedIdentifier, ex.Message); throw new DreamBadRequestException(string.Format("'{0}' is not a valid OpenID identifier. {1}", userSuppliedIdentifier, ex.Message)); } // Ask for the e-mail address on this request. // Use both SREG and AX, to increase the odds of getting it. openIdRequest.AddExtension(new ClaimsRequest{ Email = DemandLevel.Require, }); var fetch = new FetchRequest(); fetch.AddAttribute(new AttributeRequest(WellKnownAttributes.Contact.Email, true)); openIdRequest.AddExtension(fetch); // The RedirectingResponse either contains a "Location" header for // a HTTP GET, which will return in the response as 'endpoint', or // a HTML FORM which needs to be displayed to the user, which will // return in the response as 'form'. IResponse wr = openIdRequest.RedirectingResponse; XDoc result = new XDoc("openid"); if (String.IsNullOrEmpty(wr.Headers["Location"])) { System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding(); string formBody = enc.GetString(wr.Body); _log.DebugFormat("OpenID redirect by HTML FORM: {0}", formBody); result.Attr("form", formBody); } else { string redirectUrl = wr.Headers["Location"]; _log.DebugFormat("OpenID redirect URL: {0}", redirectUrl); result.Attr("endpoint", redirectUrl); } response.Return(DreamMessage.Ok(result)); yield break; }
private void AddBodyRequest(string method, XUri href, XDoc body, string type) { _requestDoc.Start("request") .Attr("method", method) .Attr("href", href.ToString()) .Attr("type", type) .Start("body") .Attr("type", "xml") .Add(body) .End() .End(); }
private void AddRequest(string method, XUri href, string data, string type) { _requestDoc.Start("request") .Attr("method", method) .Attr("href", href.ToString()) .Attr("dataid", data) .Attr("type", type) .End(); }
public void XmlAsUri() { XUri uri = new XUri("http://foo.com/bar"); XDoc doc = new XDoc("test").Elem("uri", uri.ToString()); Assert.AreEqual(uri.ToString(), doc["uri"].AsText); Assert.AreEqual(uri, doc["uri"].AsUri); }
private Result<DreamMessage> SubmitRequestAsync(string verb, XUri uri, IPrincipal user, DreamMessage request, Result<DreamMessage> response, Action completion) { if(string.IsNullOrEmpty(verb)) { if(completion != null) { completion(); } throw new ArgumentNullException("verb"); } if(uri == null) { if(completion != null) { completion(); } throw new ArgumentNullException("uri"); } if(request == null) { if(completion != null) { completion(); } throw new ArgumentNullException("request"); } if(response == null) { if(completion != null) { completion(); } throw new ArgumentNullException("response"); } // ensure environment is still running if(!IsRunning) { response.Return(DreamMessage.InternalError("host not running")); if(completion != null) { completion(); } return response; } try { Interlocked.Increment(ref _requestCounter); // check if we were not able to begin processing the request DreamMessage failed = BeginRequest(completion, uri, request); if(failed != null) { response.Return(failed); EndRequest(completion, uri, request); return response; } // check if 'verb' is overwritten by a processing parameter verb = verb.ToUpperInvariant(); string requestedVerb = (uri.GetParam(DreamInParam.VERB, null) ?? request.Headers.MethodOverride ?? verb).ToUpperInvariant(); if( verb.EqualsInvariant(Verb.POST) || ( verb.EqualsInvariant(Verb.GET) && ( requestedVerb.EqualsInvariant(Verb.OPTIONS) || requestedVerb.EqualsInvariant(Verb.HEAD) ) ) ) { verb = requestedVerb; } // check if an origin was specified request.Headers.DreamOrigin = uri.GetParam(DreamInParam.ORIGIN, request.Headers.DreamOrigin); // check if a public uri is supplied XUri publicUri = XUri.TryParse(uri.GetParam(DreamInParam.URI, null) ?? request.Headers.DreamPublicUri); XUri transport = XUri.TryParse(request.Headers.DreamTransport) ?? uri.WithoutCredentialsPathQueryFragment(); if(publicUri == null) { // check if request is local if(transport.Scheme.EqualsInvariantIgnoreCase("local")) { // local:// uris with no public-uri specifier default to the configured public-uri publicUri = _publicUri; } else { // check if the request was forwarded through Apache mod_proxy string proxyOverride = uri.GetParam(DreamInParam.HOST, null); if(string.IsNullOrEmpty(proxyOverride)) { proxyOverride = request.Headers.ForwardedHost; } string serverPath = string.Join("/", transport.Segments); if(proxyOverride != null) { // request used an override, append path of public-uri serverPath = string.Join("/", _publicUri.Segments); } // set the uri scheme based-on the incoming scheme and the override header string scheme = transport.Scheme; if("On".EqualsInvariantIgnoreCase(request.Headers.FrontEndHttps ?? "")) { scheme = Scheme.HTTPS; } scheme = uri.GetParam(DreamInParam.SCHEME, scheme); // set the host port string hostPort = proxyOverride ?? request.Headers.Host ?? uri.HostPort; publicUri = new XUri(string.Format("{0}://{1}", scheme, hostPort)).AtPath(serverPath); } request.Headers.DreamPublicUri = publicUri.ToString(); } // set host header request.Headers.Host = publicUri.HostPort; // convert incoming uri to local:// XUri localFeatureUri = uri.ChangePrefix(uri.WithoutPathQueryFragment(), _localMachineUri); // check if path begins with public uri path if((transport.Segments.Length > 0) && localFeatureUri.PathStartsWith(transport.Segments)) { localFeatureUri = localFeatureUri.WithoutFirstSegments(transport.Segments.Length); } // check if the path is the application root and whether we have special behavior for that if(localFeatureUri.Path.IfNullOrEmpty("/") == "/") { if(!string.IsNullOrEmpty(_rootRedirect)) { localFeatureUri = localFeatureUri.AtAbsolutePath(_rootRedirect); } else if(IsDebugEnv) { localFeatureUri = localFeatureUri.AtAbsolutePath("/host/services"); } } // find the requested feature List<DreamFeature> features; lock(_features) { features = _features.Find(localFeatureUri); } DreamFeature feature = null; if(features != null) { // TODO (steveb): match the incoming mime-type to the feature's acceptable mime-types (mime-type overloading) // match the request verb to the feature verb foreach(DreamFeature entry in features) { if((entry.Verb == "*") || entry.Verb.EqualsInvariant(verb)) { feature = entry; break; } } // check if this is an OPTIONS request and there is no defined feature for it if(verb.EqualsInvariant(Verb.OPTIONS) && ((feature == null) || feature.Verb.EqualsInvariant("*"))) { // list all allowed methods List<string> methods = new List<string>(); foreach(DreamFeature entry in features) { if(!methods.Contains(entry.Verb)) { methods.Add(entry.Verb); } } methods.Sort(StringComparer.Ordinal.Compare); DreamMessage result = DreamMessage.Ok(); result.Headers.Allow = string.Join(", ", methods.ToArray()); response.Return(result); // decrease counter for external requests EndRequest(completion, uri, request); return response; } } // check if a feature was found if(feature == null) { DreamMessage result; // check if any feature was found if((features == null) || (features.Count == 0)) { string msg = verb + " URI: " + uri.ToString(false) + " LOCAL: " + localFeatureUri.ToString(false) + " PUBLIC: " + publicUri + " TRANSPORT: " + transport; _log.WarnMethodCall("ProcessRequest: feature not found", msg); result = DreamMessage.NotFound("resource not found"); } else { string msg = verb + " " + uri.ToString(false); _log.WarnMethodCall("ProcessRequest: method not allowed", msg); List<string> methods = new List<string>(); foreach(DreamFeature entry in features) { if(!methods.Contains(entry.Verb)) { methods.Add(entry.Verb); } } methods.Sort(StringComparer.Ordinal.Compare); result = DreamMessage.MethodNotAllowed(methods.ToArray(), "allowed methods are " + string.Join(", ", methods.ToArray())); } response.Return(result); // decrease counter for external requests EndRequest(completion, uri, request); return response; } // add uri to aliases list if(_memorizeAliases) { lock(_aliases) { _aliases[transport] = transport; _aliases[publicUri] = publicUri; } } // create context DreamContext context = new DreamContext(this, verb, localFeatureUri, feature, publicUri, _publicUri, request, CultureInfo.InvariantCulture, GetRequestLifetimeScopeFactory(feature.Service)); // attach request id to the context context.SetState(DreamHeaders.DREAM_REQUEST_ID, request.Headers.DreamRequestId); // add user to context context.User = user; // build linked-list of feature calls var chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(result => { // extract message DreamMessage message; if(result.HasValue) { message = result.Value; } else if(result.Exception is DreamAbortException) { message = ((DreamAbortException)result.Exception).Response; } else if(result.Exception is DreamCachedResponseException) { message = ((DreamCachedResponseException)result.Exception).Response; } else { _log.ErrorExceptionFormat(response.Exception, "Failed Feature '{0}' Chain [{1}:{2}]: {3}", feature.MainStage.Name, verb, localFeatureUri.Path, response.Exception.Message ); message = DreamMessage.InternalError(result.Exception); } // decrease counter for external requests EndRequest(completion, uri, request); // need to manually dispose of the context, since we're already attaching and detaching it by hand to TaskEnvs throughout the chain if(response.IsCanceled) { _log.DebugFormat("response for '{0}' has already returned", context.Uri.Path); response.ConfirmCancel(); ((ITaskLifespan)context).Dispose(); } else { ((ITaskLifespan)context).Dispose(); response.Return(message); } }); for(int i = feature.Stages.Length - 1; i >= 0; --i) { var link = new DreamFeatureChain(feature.Stages[i], i == feature.MainStageIndex, context, chain, (i > 0) ? feature.Stages[i - 1].Name : "first"); chain = new Result<DreamMessage>(TimeSpan.MaxValue, TaskEnv.Current).WhenDone(link.Handler); } // kick-off new task AsyncUtil.Fork( () => chain.Return(request), TaskEnv.New(TimerFactory), new Result(TimeSpan.MaxValue, response.Env).WhenDone(res => { if(!res.HasException) { return; } _log.ErrorExceptionFormat(res.Exception, "handler for {0}:{1} failed", context.Verb, context.Uri.ToString(false)); ((ITaskLifespan)context).Dispose(); // forward exception to recipient response.Throw(res.Exception); // decrease counter for external requests EndRequest(completion, uri, request); }) ); } catch(Exception e) { response.Throw(e); EndRequest(completion, uri, request); } return response; }
private void StopService(XUri uri) { string path = uri.Path.ToLowerInvariant(); // remove service from services table ServiceEntry service; lock(_services) { if(_services.TryGetValue(path, out service)) { _services.Remove(path); } } if(_log.IsDebugEnabled) { string sid = "<UNKNOWN>"; string type = "<UNKNOWN>"; if(service != null) { sid = service.SID.ToString(); if(service.Service != null) { type = service.Service.GetType().ToString(); } } _log.DebugMethodCall("stop", path, sid, type); } // deactivate service DreamMessage deleteResponse = Plug.New(uri).At("@config").Delete(new Result<DreamMessage>(TimeSpan.MaxValue)).Wait(); if(!deleteResponse.IsSuccessful) { _log.InfoMethodCall("StopService: Delete failed", uri.ToString(false), deleteResponse.Status); } // deactivate features lock(_features) { _features.Remove(uri); } // check for private-storage service if((service != null) && !service.Blueprint["setup/private-storage"].IsEmpty) { StopService(Self.At("private-storage", EncodedServicePath(service.Uri))); } // check for any lingering child services List<ServiceEntry> entries; lock(_services) { entries = _services.Values.Where(entry => uri == entry.Owner).ToList(); } foreach(ServiceEntry entry in entries) { _log.WarnMethodCall("StopService: child service was not shutdown properly", entry.Service.Self.Uri.ToString(false)); StopService(entry.Service.Self); } }
//--- Methods --- private Yield FetchResult(string name, XUri input, Hashtable args, Result<XDoc> response) { // build uri XUri uri = new XUri("http://www.dapper.net/RunDapp?v=1").With("dappName", name); if(input != null) { uri = uri.With("applyToUrl", input.ToString()); } if(args != null) { foreach(DictionaryEntry entry in args) { uri = uri.With(VARIABLE_PREFIX + SysUtil.ChangeType<string>(entry.Key), SysUtil.ChangeType<string>(entry.Value)); } } // check if we have a cached result XDoc result; string key = uri.ToString(); lock(_cache) { if(_cache.TryGetValue(key, out result)) { response.Return(result); yield break; } } // fetch result Result<DreamMessage> res; yield return res = Plug.New(uri).GetAsync(); if(!res.Value.IsSuccessful) { throw new DreamInternalErrorException(string.Format("Unable to process Dapp: ", input)); } if(!res.Value.HasDocument) { throw new DreamInternalErrorException(string.Format("Dapp response is not XML: ", input)); } // add result to cache and start a clean-up timer lock(_cache) { _cache[key] = res.Value.ToDocument(); } TaskTimer.New(TimeSpan.FromSeconds(CACHE_TTL), RemoveCachedEntry, key, TaskEnv.None); response.Return(res.Value.ToDocument()); yield break; }
private Yield Process(XUri image, string options, Result<XUri> result) { // TODO (steveb): // 1) check if the fetched image has caching information // 2) if it does, check if it changed since last operation // 3) generate MD5 from image (not from uri) // 4) respond with new uri if(image == null) { throw new DreamBadRequestException("invalid image uri"); } // check if we have a cached response string key = new Guid(StringUtil.ComputeHash(image.ToString() + " " + options)).ToString() + ".png"; Plug location = Storage.At(key); Result<DreamMessage> res; yield return res = location.InvokeAsync("HEAD", DreamMessage.Ok()); if(res.Value.IsSuccessful) { result.Return(Self.At("images", key)); yield break; } // check if image-magick is properly configured if(string.IsNullOrEmpty(ImageMagick) || !File.Exists(ImageMagick)) { throw new DreamBadRequestException("imagemagick misconfigured or missing"); } // fetch image res = new Result<DreamMessage>(); Plug.New(image).InvokeEx("GET", DreamMessage.Ok(), res); yield return res; if(!res.Value.IsSuccessful) { res.Value.Close(); throw new DreamAbortException(DreamMessage.NotFound("could not retrieve image")); } // invoke image conversion Result<Tuplet<int, Stream, Stream>> exitRes = new Result<Tuplet<int, Stream, Stream>>(TimeSpan.FromSeconds(30)); using(Stream input = res.Value.AsStream()) { yield return Async.ExecuteProcess(ImageMagick, string.Format("{0} {1}:-", options, "png"), input, exitRes); } // check outcome int status = exitRes.Value.Item1; Stream output = exitRes.Value.Item2; Stream error = exitRes.Value.Item3; if(status != 0) { string message; using(StreamReader reader = new StreamReader(error, Encoding.ASCII)) { message = reader.ReadToEnd(); } throw new DreamAbortException(DreamMessage.InternalError(string.Format("operation failed with status {0}:\n{1}", status, message))); } // create result yield return res = location.With("ttl", TimeSpan.FromDays(7).TotalSeconds).PutAsync(DreamMessage.Ok(MimeType.PNG, output.Length, output)); if(!res.Value.IsSuccessful) { throw new DreamAbortException(new DreamMessage(res.Value.Status, null, res.Value.ContentType, res.Value.AsBytes())); } result.Return(Self.At("images", key)); }
private XDoc RewriteRedirectsAndUris(XDoc searchResults) { DekiContext context = DekiContext.Current; // setup proper URI for file attachments and users foreach(XDoc node in searchResults["document"]) { try { if(node["type"].AsText.EqualsInvariant("wiki")) { node["uri"].ReplaceValue(Utils.AsPublicUiUri(Title.FromUriPath(node["path"].AsText))); } else if(node["type"].AsText.EqualsInvariant("comment")) { XUri commentUri = node["uri"].AsUri; XUri publicCommentUri = new XUri(Utils.AsPublicUiUri(Title.FromUriPath(node["path"].AsText))).WithFragment(commentUri.Fragment); node["uri"].ReplaceValue(publicCommentUri.ToString()); } else if(!node["id.file"].IsEmpty) { node["uri"].ReplaceValue(context.ApiUri.At("files", node["id.file"].AsText, Title.AsApiParam(node["title"].AsText))); } else if(!node["id.user"].IsEmpty) { node["uri"].ReplaceValue(new XUri(Utils.AsPublicUiUri(Title.FromUIUsername(node["username"].AsText)))); } } catch(Exception e) { _log.Warn(string.Format("Unable to generate UI uri for API Uri '{0}'", node["uri"].AsText), e); } } return searchResults; }
public void Authtoken_recipients_always_get_what_they_want() { XUri mockDeki = new XUri("http://mock/deki"); int dekiCalled = 0; MockPlug.Register(mockDeki, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { _log.DebugFormat("deki called at {0}", u); dekiCalled++; r2.Return(DreamMessage.Ok()); }); XUri mockAuthorized = new XUri("http://mock/authorized"); int authorizedCalled = 0; XDoc received = null; var authorizedResetEvent = new ManualResetEvent(false); MockPlug.Register(mockAuthorized, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { authorizedCalled++; received = r.ToDocument(); authorizedResetEvent.Set(); r2.Return(DreamMessage.Ok()); }); XUri mockNotAuthorized = new XUri("http://mock/notauthorized"); int notAuthorizedCalled = 0; MockPlug.Register(mockNotAuthorized, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { notAuthorizedCalled++; r2.Return(DreamMessage.Ok()); }); DreamServiceInfo serviceInfo = DreamTestHelper.CreateService( _hostInfo, "sid://mindtouch.com/dream/2008/10/pubsub", "authorization", new XDoc("config") .Elem("authtoken", "abc") .Elem("uri.deki", mockDeki) .Start("components") .Attr("context", "service") .Start("component") .Attr("implementation", typeof(DekiDispatcher).AssemblyQualifiedName) .Attr("type", typeof(IPubSubDispatcher).AssemblyQualifiedName) .End() .End() ); XDoc subForAuthorized = new XDoc("subscription-set") .Elem("uri.owner", mockAuthorized) .Start("subscription") .Attr("id", "1") .Elem("uri.resource", "http://mock/resource/*") .Elem("channel", "channel:///foo/*") .Start("recipient").Attr("authtoken", "abc").Elem("uri", mockAuthorized).End() .End(); DreamMessage result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(subForAuthorized).Wait(); Assert.IsTrue(result.IsSuccessful); XDoc subForNotAuthorized = new XDoc("subscription-set") .Elem("uri.owner", mockNotAuthorized) .Start("subscription") .Attr("id", "1") .Elem("uri.resource", "http://mock/resource/*") .Elem("channel", "channel:///foo/*") .Start("recipient").Elem("uri", mockNotAuthorized).End() .End(); result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(subForNotAuthorized).Wait(); Assert.IsTrue(result.IsSuccessful); XDoc msg = new XDoc("foop"); result = serviceInfo.WithInternalKey() .AtLocalHost .At("publish") .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar") .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString()) .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/bar") .PostAsync(msg).Wait(); Assert.IsTrue(result.IsSuccessful); // Meh. Testing multithreaded code is wonky. This 1000ms sleep is required, otherwise the event below may not fire Thread.Sleep(1000); Assert.IsTrue(authorizedResetEvent.WaitOne(2000, true)); Assert.AreEqual(0, dekiCalled); Assert.AreEqual(0, notAuthorizedCalled); Assert.AreEqual(1, authorizedCalled); Assert.AreEqual(msg, received); }
public void Uses_deki_to_prune_recipients() { XUri mockDeki = new XUri("http://mock/deki"); int dekiCalled = 0; int dekipage42authCalled = 0; bool dekiArgsGood = false; int dekipage43authCalled = 0; MockPlug.Register(mockDeki, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { _log.DebugFormat("mockDeki called at: {0}", u); dekiCalled++; dekiArgsGood = false; List<int> users = new List<int>(); foreach(XDoc user in r.ToDocument()["user/@id"]) { users.Add(user.AsInt.Value); } if(users.Count == 4 && users.Contains(1) && users.Contains(2) && users.Contains(3) && users.Contains(4)) { dekiArgsGood = true; } DreamMessage msg = DreamMessage.Ok(); if(u.WithoutQuery() == mockDeki.At("pages", "42", "allowed")) { dekipage42authCalled++; msg = DreamMessage.Ok(new XDoc("users") .Start("user").Attr("id", 1).End() .Start("user").Attr("id", 2).End()); } else if(u.WithoutQuery() == mockDeki.At("pages", "43", "allowed")) { dekipage43authCalled++; msg = DreamMessage.Ok(new XDoc("users") .Start("user").Attr("id", 3).End() .Start("user").Attr("id", 4).End()); } r2.Return(msg); }); XUri mockRecipient = new XUri("http://mock/r1"); int mockRecipientCalled = 0; XDoc received = null; List<string> recipients = new List<string>(); AutoResetEvent are = new AutoResetEvent(false); MockPlug.Register(mockRecipient, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { _log.DebugFormat("mockRecipient called at: {0}", u); mockRecipientCalled++; received = r.ToDocument(); recipients.Clear(); recipients.AddRange(r.Headers.DreamEventRecipients); are.Set(); r2.Return(DreamMessage.Ok()); }); DreamServiceInfo serviceInfo = DreamTestHelper.CreateService( _hostInfo, "sid://mindtouch.com/dream/2008/10/pubsub", "whitelist", new XDoc("config") .Elem("uri.deki", mockDeki) .Start("components") .Attr("context", "service") .Start("component") .Attr("implementation", typeof(DekiDispatcher).AssemblyQualifiedName) .Attr("type", typeof(IPubSubDispatcher).AssemblyQualifiedName) .End() .End() ); XDoc sub = new XDoc("subscription-set") .Elem("uri.owner", mockRecipient) .Start("subscription") .Attr("id", "1") .Elem("uri.resource", "http://mock/resource/x") .Elem("channel", "channel:///foo/*") .Elem("uri.proxy", mockRecipient) .Start("recipient").Attr("userid", "1").Elem("uri", "http://recipient/a").End() .Start("recipient").Attr("userid", "2").Elem("uri", "http://recipient/b").End() .Start("recipient").Attr("userid", "3").Elem("uri", "http://recipient/c").End() .Start("recipient").Attr("userid", "4").Elem("uri", "http://recipient/d").End() .End() .Start("subscription") .Attr("id", "2") .Elem("uri.resource", "http://mock/resource/y") .Elem("channel", "channel:///foo/*") .Elem("uri.proxy", mockRecipient) .Start("recipient").Attr("userid", "1").Elem("uri", "http://recipient/a").End() .Start("recipient").Attr("userid", "2").Elem("uri", "http://recipient/b").End() .Start("recipient").Attr("userid", "3").Elem("uri", "http://recipient/c").End() .Start("recipient").Attr("userid", "4").Elem("uri", "http://recipient/d").End() .End(); DreamMessage result = serviceInfo.WithInternalKey().AtLocalHost.At("subscribers").PostAsync(sub).Wait(); Assert.IsTrue(result.IsSuccessful); XDoc ev = new XDoc("event").Elem("pageid", 42); result = serviceInfo.WithInternalKey() .AtLocalHost .At("publish") .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar") .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString()) .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/x") .PostAsync(ev).Wait(); Assert.IsTrue(result.IsSuccessful); Assert.IsTrue(are.WaitOne(500, true)); Assert.AreEqual(1, dekiCalled); Assert.AreEqual(1, dekipage42authCalled); Assert.IsTrue(dekiArgsGood); Assert.AreEqual(ev, received); Assert.AreEqual(2, recipients.Count); Assert.Contains("http://recipient/a", recipients); Assert.Contains("http://recipient/b", recipients); ev = new XDoc("event").Elem("pageid", 43); result = serviceInfo.WithInternalKey() .AtLocalHost .At("publish") .WithHeader(DreamHeaders.DREAM_EVENT_CHANNEL, "channel:///foo/bar") .WithHeader(DreamHeaders.DREAM_EVENT_ORIGIN, mockDeki.ToString()) .WithHeader(DreamHeaders.DREAM_EVENT_RESOURCE, "http://mock/resource/y") .PostAsync(ev).Wait(); Assert.IsTrue(result.IsSuccessful); Assert.IsTrue(are.WaitOne(5000, true)); Assert.AreEqual(2, dekiCalled); Assert.AreEqual(1, dekipage42authCalled); Assert.IsTrue(dekiArgsGood); Assert.AreEqual(ev, received); Assert.AreEqual(2, recipients.Count); Assert.Contains("http://recipient/c", recipients); Assert.Contains("http://recipient/d", recipients); }
public void Subscribe_and_see_page_change_dispatched() { XDoc set = null, sub = null; // make sure we have a pagesubservice subscription to start with Assert.IsTrue(Wait.For(() => { set = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).Get(new Result<XDoc>()).Wait(); return set["subscription[channel='event://*/deki/pages/update']"] .Where(x => x["recipient/uri"].AsText.EndsWithInvariant("deki/pagesubservice/notify")).Any(); }, 10.Seconds()), set.ToPrettyString()); _log.Debug("start: Subscribe_and_see_page_change_dispatched"); var emailResetEvent = new ManualResetEvent(false); // subscribe to dekipubsub, so we can verify page creation event has passed XUri coreSubscriber = new XUri("http://mock/dekisubscriber"); var createdPages = new HashSet<string>(); var modifiedPages = new HashSet<string>(); MockPlug.Register(coreSubscriber, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { var doc = r.ToDocument(); var channel = doc["channel"].AsUri; var pId = doc["pageid"].AsText; _log.DebugFormat("dekisubscriber called called with channel '{0}' for page {1}", channel, pId); if(channel.LastSegment == "create") { lock(createdPages) { createdPages.Add(pId); } } else if(channel.LastSegment == "update") { lock(modifiedPages) { modifiedPages.Add(pId); } } else { _log.Info("wrong channel!"); } r2.Return(DreamMessage.Ok()); }); XDoc subscriptionSet = new XDoc("subscription-set") .Elem("uri.owner", coreSubscriber) .Start("subscription") .Elem("channel", "event://*/deki/pages/*") .Start("recipient") .Attr("authtoken", Utils.Settings.HostInfo.ApiKey) .Elem("uri", coreSubscriber) .End() .End(); var subscriptionResult = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).PostAsync(subscriptionSet).Wait(); Assert.IsTrue(subscriptionResult.IsSuccessful); _log.DebugFormat("check for subscription in combined set"); Assert.IsTrue(Wait.For(() => { set = Utils.Settings.HostInfo.LocalHost.At("deki", "pubsub", "subscribers").With("apikey", Utils.Settings.HostInfo.ApiKey).Get(new Result<XDoc>()).Wait(); sub = set["subscription[channel='event://*/deki/pages/*']"]; return sub["recipient/uri"].Contents == coreSubscriber.ToString(); }, 10.Seconds())); int emailerCalled = 0; XDoc emailDoc = null; DreamMessage response; string pageId; string pagePath; // create a page _log.DebugFormat("creating page"); response = PageUtils.CreateRandomPage(_adminPlug, out pageId, out pagePath); Assert.IsTrue(response.IsSuccessful); //give the page creation event a chance to bubble through _log.DebugFormat("checking for page {0} creation event", pageId); Assert.IsTrue(Wait.For(() => { Thread.Sleep(100); lock(createdPages) { return createdPages.Contains(pageId); } }, 10.Seconds())); _log.DebugFormat("got create event"); _log.DebugFormat("post page subscription for page {0}", pageId); response = _pageSub.At("pages", pageId).With("depth", "0").WithHeader("X-Deki-Site", "id=default").PostAsFormAsync().Wait(); Assert.IsTrue(response.IsSuccessful); XUri emailerEndpoint = Utils.Settings.HostInfo.Host.LocalMachineUri.At("deki", "mailer"); MockPlug.Register(emailerEndpoint, delegate(Plug p, string v, XUri u, DreamMessage r, Result<DreamMessage> r2) { var doc = r.ToDocument(); var to = doc["to"].AsText; _log.DebugFormat("emailer called for: {0}", to); var fragment = _userName + "@"; if(to.StartsWith(fragment) || to.Contains(fragment)) { emailDoc = doc; emailerCalled++; emailResetEvent.Set(); } r2.Return(DreamMessage.Ok()); }); _log.Debug("mod page"); modifiedPages.Clear(); response = PageUtils.SavePage(Utils.BuildPlugForAdmin(), pagePath, "foo"); Assert.IsTrue(response.IsSuccessful); _log.DebugFormat("checking for page {0} update event", pageId); Assert.IsTrue(Wait.For(() => { Thread.Sleep(100); lock(modifiedPages) { return modifiedPages.Contains(pageId); } }, 10.Seconds())); _log.DebugFormat("got update event"); _log.Debug("waiting on email post"); Assert.IsTrue(Wait.For(() => emailResetEvent.WaitOne(100, true), 10.Seconds())); _log.Debug("email fired"); Assert.IsFalse(emailDoc.IsEmpty); Assert.AreEqual(pageId, emailDoc["pages/pageid"].AsText); Thread.Sleep(200); Assert.AreEqual(1, emailerCalled); }
public virtual DekiScriptLiteral Invoke(Location location, XUri uri, DekiScriptLiteral args, DekiScriptEnv env) { var sw = Stopwatch.StartNew(); DekiScriptInvocationTargetDescriptor descriptor; var target = _functions.TryGetValue(uri, out descriptor) ? descriptor.Target : FindTarget(uri); try { // invoke function directly return target.Invoke(this, args); } catch(Exception e) { throw UnwrapAsyncException(uri, e).Rethrow(); } finally { sw.Stop(); bool property = (uri.LastSegment ?? string.Empty).StartsWithInvariant("$"); env.AddFunctionProfile(location, (descriptor != null) ? (property ? "$" : "") + descriptor.Name : uri.ToString(), sw.Elapsed); } }