Exemple #1
0
 /// <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);
 }
Exemple #2
0
        //--- 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);
                }
            }
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        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()
                                            ));
        }
Exemple #5
0
        /// <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();
        }
Exemple #6
0
        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());
        }
Exemple #7
0
        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;
        }
Exemple #8
0
 public virtual Yield GetServiceBlueprint(DreamContext context, DreamMessage request, Result <DreamMessage> response)
 {
     response.Return(DreamMessage.Ok(Blueprint));
     yield break;
 }
Exemple #9
0
        /// <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);
        }