/// <summary> /// Invoke the plug with the <see cref="Verb.GET"/> verb and no message body and return as a document result. /// </summary> /// <remarks> /// Since this method goes straight from a <see cref="DreamMessage"/> to a document, this method will set a /// <see cref="DreamResponseException"/> on the result, if <see cref="DreamMessage.IsSuccessful"/> is <see langword="False"/>. /// </remarks> /// <param name="plug">Plug instance to invoke.</param> /// <param name="result">The <see cref="Result{XDoc}"/>instance to be returned by this method.</param> /// <returns>Synchronization handle.</returns> public static Result <XDoc> Get(this Plug plug, Result <XDoc> result) { plug.Invoke(Verb.GET, DreamMessage.Ok(), new Result <DreamMessage>()).WhenDone(r => { if (r.HasException) { result.Throw(r.Exception); } else if (!r.Value.IsSuccessful) { result.Throw(new DreamResponseException(r.Value)); } else { result.Return(r.Value.ToDocument()); } }); return(result); }
//--- Methods --- /// <summary> /// Invoke the stage method. /// </summary> /// <param name="context"><see cref="DreamContext"/> for invocation.</param> /// <param name="request"><see cref="DreamMessage"/> for invocation.</param> /// <param name="response"><see cref="Result{DreamMessage}"/> for invocations.</param> public void Invoke(DreamContext context, DreamMessage request, Result <DreamMessage> response) { if (_handler != null) { Coroutine.Invoke(_handler, context, request, response); } else { try { // build parameter list var arguments = new object[_plan.Count]; for (var i = 0; i < _plan.Count; ++i) { try { arguments[i] = _plan[i].Invoke(context, request, response); } catch (DreamException) { throw; } catch (Exception e) { throw new FeatureArgumentParseException(_plan[i].ArgumentName, e); } } // invoke method if (_method.ReturnType == typeof(Yield)) { // invoke method as coroutine new Coroutine(_method, response).Invoke(() => (Yield)_method.InvokeWithRethrow(_service, arguments)); } else if (_method.ReturnType == typeof(XDoc)) { // invoke method to get XDoc response (always an Ok) var doc = _method.InvokeWithRethrow(_service, arguments) as XDoc; response.Return(DreamMessage.Ok(doc)); } else { response.Return((DreamMessage)_method.InvokeWithRethrow(_service, arguments)); } } catch (Exception e) { response.Throw(e); } } }
/// <summary> /// WARNING: This method is thread-blocking. Please avoid using it if possible. /// </summary> internal static XDoc ExecuteAction(Plug env, DreamHeaders headers, XDoc action) { string verb = action["@verb"].Contents; string path = action["@path"].Contents; if ((path.Length > 0) && (path[0] == '/')) { path = path.Substring(1); } XUri uri; if (!XUri.TryParse(path, out uri)) { uri = env.Uri.AtAbsolutePath(path); } // create message DreamMessage message = DreamMessage.Ok(GetActionBody(action)); message.Headers.AddRange(headers); // apply headers foreach (XDoc header in action["header"]) { message.Headers[header["@name"].Contents] = header.Contents; } // BUG #814: we need to support events // execute action DreamMessage reply = Plug.New(uri).Invoke(verb, message); // prepare response XDoc result = new XMessage(reply); string ID = action["@ID"].Contents; if (!string.IsNullOrEmpty(ID)) { result.Root.Attr("ID", ID); } return(result); }
protected virtual Yield PutConfig(DreamContext context, DreamMessage request, Result <DreamMessage> response) { XDoc config = request.ToDocument(); if (config.Name != "config") { throw new DreamBadRequestException("bad document type"); } if (IsStarted) { throw new DreamBadRequestException("service must be stopped first"); } _timerFactory = TaskTimerFactory.Create(this); // configure service container var components = config["components"]; var servicecontainer = _env.CreateServiceContainer(this); var builder = new ContainerBuilder(); builder.Register(_timerFactory).ExternallyOwned(); if (!components.IsEmpty) { _log.Debug("registering service level module"); builder.RegisterModule(new XDocAutofacContainerConfigurator(components, DreamContainerScope.Service)); } builder.Build(servicecontainer); // call container-less start (which contains shared start logic) yield return(Coroutine.Invoke(Start, request.ToDocument(), new Result())); // call start with container for sub-classes that want to resolve instances at service start yield return(Coroutine.Invoke(Start, config, servicecontainer, new Result())); response.Return(DreamMessage.Ok(new XDoc("service-info") .Start("private-key") .Add(DreamCookie.NewSetCookie("service-key", PrivateAccessKey, Self.Uri).AsSetCookieDocument) .End() .Start("internal-key") .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument) .End() )); }
/// <summary> /// Perform startup configuration of a service instance. /// </summary> /// <remarks> /// Should not be manually invoked and should only be overridden if <see cref="Start(MindTouch.Xml.XDoc,Autofac.ILifetimeScope,MindTouch.Tasking.Result)"/> isn't already overriden. /// </remarks> /// <param name="config">Service configuration.</param> /// <param name="result">Synchronization handle for coroutine invocation.</param> /// <returns>Iterator used by <see cref="Coroutine"/> execution environment.</returns> protected virtual Yield Start(XDoc config, Result result) { Result <DreamMessage> res; // store configuration and uri _config = config; _self = Plug.New(config["uri.self"].AsUri); if (_self == null) { throw new ArgumentNullException("config", "Missing element'uri.self'"); } _owner = Plug.New(config["uri.owner"].AsUri); // check for service access keys var internalAccessKey = config["internal-service-key"].AsText; if (!string.IsNullOrEmpty(internalAccessKey)) { _internalAccessKey = internalAccessKey; } var privateAccessKey = config["private-service-key"].AsText; if (!string.IsNullOrEmpty(privateAccessKey)) { _privateAccessKey = privateAccessKey; } // check for api-key settings _apikey = config["apikey"].AsText; // process 'set-cookie' entries var setCookies = DreamCookie.ParseAllSetCookieNodes(config["set-cookie"]); if (setCookies.Count > 0) { Cookies.Update(setCookies, null); } // grant private access key to self, host, and owner var privateAcccessCookie = DreamCookie.NewSetCookie("service-key", PrivateAccessKey, Self.Uri); Cookies.Update(privateAcccessCookie, null); yield return(Env.At("@grants").Post(DreamMessage.Ok(privateAcccessCookie.AsSetCookieDocument), new Result <DreamMessage>(TimeSpan.MaxValue))); if (Owner != null) { yield return(res = Owner.At("@grants").Post(DreamMessage.Ok(privateAcccessCookie.AsSetCookieDocument), new Result <DreamMessage>(TimeSpan.MaxValue))); if (!res.Value.IsSuccessful) { throw new ArgumentException("unexpected failure setting grants on owner service"); } } // check if this service requires a service-license to work if (this is IDreamServiceLicense) { string service_license = config["service-license"].AsText; if (string.IsNullOrEmpty(service_license)) { throw new DreamAbortException(DreamMessage.LicenseRequired("service-license missing")); } // extract public RSA key for validation RSACryptoServiceProvider public_key = RSAUtil.ProviderFrom(GetType().Assembly); if (public_key == null) { throw new DreamAbortException(DreamMessage.InternalError("service assembly invalid")); } // validate the service-license _license = null; Dictionary <string, string> values; try { // parse service-license values = HttpUtil.ParseNameValuePairs(service_license); if (!Encoding.UTF8.GetBytes(service_license.Substring(0, service_license.LastIndexOf(','))).VerifySignature(values["dsig"], public_key)) { throw new DreamAbortException(DreamMessage.InternalError("invalid service-license (1)")); } // check if the SID matches string sid; if (!values.TryGetValue("sid", out sid) || !SID.HasPrefix(XUri.TryParse(sid), true)) { throw new DreamAbortException(DreamMessage.InternalError("invalid service-license (2)")); } _license = service_license; } catch (Exception e) { // unexpected error, blame it on the license if (e is DreamAbortException) { throw; } throw new DreamAbortException(DreamMessage.InternalError("corrupt service-license (1)")); } // validate expiration date string expirationtext; if (values.TryGetValue("expire", out expirationtext)) { try { DateTime expiration = DateTime.Parse(expirationtext); if (expiration < DateTime.UtcNow) { _license = null; } } catch (Exception e) { _license = null; // unexpected error, blame it on the license if (e is DreamAbortException) { throw; } throw new DreamAbortException(DreamMessage.InternalError("corrupt service-license (2)")); } } // check if a license was assigned if (_license == null) { throw new DreamAbortException(DreamMessage.LicenseRequired("service-license has expired")); } } else { config["service-license"].RemoveAll(); } // create built-in services _storage = Plug.New(config["uri.storage"].AsUri); _pubsub = Plug.New(config["uri.pubsub"].AsUri); // done _log.Debug("Start"); result.Return(); }
protected virtual Yield DeleteService(DreamContext context, DreamMessage request, Result <DreamMessage> response) { yield return(Env.At("stop").Post(new XDoc("service").Elem("uri", Self), new Result <DreamMessage>(TimeSpan.MaxValue)).CatchAndLog(_log)); response.Return(DreamMessage.Ok()); }
public virtual Yield GetServiceInfo(DreamContext context, DreamMessage request, Result <DreamMessage> response) { XDoc blueprint = Blueprint; string title = blueprint["name"].AsText ?? "Service Blueprint"; XDoc result = new XDoc("html").Attr("xmlns", "http://www.w3.org/1999/xhtml") .Start("head") .Elem("title", title) .Start("meta").Attr("http-equiv", "content-type").Attr("content", "text/html;charset=utf-8").End() .Start("meta").Attr("http-equiv", "Content-Style-Type").Attr("content", "text/css").End() .End(); if (blueprint.IsEmpty) { result.Elem("body", "Missing service blueprint"); } else { result.Start("body") .Elem("h1", title) .Start("p") .Value(blueprint["copyright"].Contents) .Value(" ") .Start("a").Attr("href", blueprint["info"].Contents).Value("(more)").End() .Value(" ") .Start("a").Attr("href", Self.Uri.At("@blueprint").Path).Value("(blueprint)").End() .End(); // show configuration information XDoc config = blueprint["configuration"]; if (!config.IsEmpty) { result.Elem("h2", "Configuration"); result.Start("ul"); foreach (XDoc entry in config["entry"]) { result.Start("li"); if (entry["valuetype"].Contents != string.Empty) { result.Value(string.Format("{0} = {1} : {2}", entry["name"].Contents, entry["valuetype"].Contents, entry["description"].Contents)); } else { result.Value(string.Format("{0} : {1}", entry["name"].Contents, entry["description"].Contents)); } result.End(); } result.End(); } // sort features by signature then verb blueprint["features"].Sort(delegate(XDoc first, XDoc second) { string[] firstPattern = first["pattern"].Contents.Split(new[] { ':' }, 2); string[] secondPattern = second["pattern"].Contents.Split(new[] { ':' }, 2); int cmp = firstPattern[1].CompareInvariantIgnoreCase(secondPattern[1]); if (cmp != 0) { return(cmp); } return(firstPattern[0].CompareInvariant(secondPattern[0])); }); // display features XDoc features = blueprint["features/feature"]; if (!features.IsEmpty) { result.Elem("h2", "Features"); List <string> modifiers = new List <string>(); foreach (XDoc feature in features) { modifiers.Clear(); // add modifiers string modifier = feature["access"].AsText; if (modifier != null) { modifiers.Add(modifier); } modifier = feature["obsolete"].AsText; if (modifier != null) { modifiers.Add("OBSOLETE => " + modifier); } if (modifiers.Count > 0) { modifier = " (" + string.Join(", ", modifiers.ToArray()) + ")"; } else { modifier = string.Empty; } // check if feature has GET verb and no path parameters string pattern = feature["pattern"].Contents; if (pattern.StartsWithInvariantIgnoreCase(Verb.GET + ":") && (pattern.IndexOfAny(new[] { '{', '*', '?' }) == -1)) { string[] parts = pattern.Split(new[] { ':' }, 2); result.Start("h3") .Start("a").Attr("href", context.AsPublicUri(Self.Uri.AtPath(parts[1]))) .Value(feature["pattern"].Contents) .End() .Value(modifier) .End(); } else { result.Elem("h3", feature["pattern"].Contents + modifier); } result.Start("p") .Value(feature["description"].Contents) .Value(" ") .Start("a").Attr("href", feature["info"].Contents).Value("(more)").End(); XDoc paramlist = feature["param"]; if (!paramlist.IsEmpty) { result.Start("ul"); foreach (XDoc param in paramlist) { result.Start("li"); if (param["valuetype"].Contents != string.Empty) { result.Value(string.Format("{0} = {1} : {2}", param["name"].Contents, param["valuetype"].Contents, param["description"].Contents)); } else { result.Value(string.Format("{0} : {1}", param["name"].Contents, param["description"].Contents)); } result.End(); } result.End(); } result.End(); } } } response.Return(DreamMessage.Ok(MimeType.HTML, result.ToString())); yield break; }
public virtual Yield GetServiceBlueprint(DreamContext context, DreamMessage request, Result <DreamMessage> response) { response.Return(DreamMessage.Ok(Blueprint)); yield break; }
/// <summary> /// Invoke an action in the context of a service feature. /// </summary> /// <remarks> /// Assumes that there exists a current <see cref="DreamContext"/> that belongs to a request to another feature of this service. /// </remarks> /// <param name="verb">Http verb.</param> /// <param name="path">Feature path.</param> /// <param name="handler">Action to perform in this context.</param> /// <returns>Exception thrown by handler or null.</returns> public Exception InvokeInServiceContext(string verb, string path, Action handler) { if (handler == null) { throw new ArgumentNullException("handler"); } if (string.IsNullOrEmpty(verb)) { throw new ArgumentNullException("verb"); } if (path == null) { throw new ArgumentNullException("path"); } // create new new environment for execution XUri uri = Self.AtPath(path); DreamContext current = DreamContext.Current; Exception e = TaskEnv.ExecuteNew(delegate { DreamFeatureStage[] stages = new[] { new DreamFeatureStage("InServiceInvokeHandler", InServiceInvokeHandler, DreamAccess.Private) }; // BUGBUGBUG (steveb): when invoking a remote function this way, we're are not running the proloques and epilogues, which leads to errors; // also dream-access attributes are being ignored (i.e. 'public' vs. 'private') DreamMessage message = DreamUtil.AppendHeadersToInternallyForwardedMessage(current.Request, DreamMessage.Ok()); var context = current.CreateContext(verb, uri, new DreamFeature(this, Self, 0, stages, verb, path), message); context.AttachToCurrentTaskEnv(); // pass along host and public-uri information handler(); }, TimerFactory); return(e); }