/// <summary> /// Async implementation of signal /// </summary> /// <param name="signaler">Signaler used to signal</param> /// <param name="input">Parameters passed from signaler</param> public async Task SignalAsync(ISignaler signaler, Node input) { var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { var whitelist = GetWhitelist(input); await signaler.ScopeAsync("whitelist", whitelist.Vocabulary, async() => { await signaler.SignalAsync("eval", whitelist.Lambda.Clone()); }); input.Clear(); input.Value = result.Value; input.AddRange(result.Children.ToList()); }); }
/// <summary> /// [mssql.transaction.create] slot for creating a new MS SQL database transaction. /// </summary> /// <param name="signaler">Signaler used to raise the signal.</param> /// <param name="input">Arguments to your slot.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { await signaler.ScopeAsync( "mssql.transaction", new Transaction(signaler, signaler.Peek <SqlConnectionWrapper>("mssql.connect").Connection), async() => await signaler.SignalAsync("eval", input)); }
/// <summary> /// Implementation of slot. /// </summary> /// <param name="signaler">Signaler used to raise the signal.</param> /// <param name="input">Arguments to slot.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { // Making sure we're able to handle returned values and nodes from slot implementation. var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { // Loading file and converting its content to lambda. var filename = input.GetEx <string>(); var hyperlambda = await _service.LoadAsync( PathResolver.CombinePaths( _rootResolver.RootFolder, filename)); var lambda = new Parser(hyperlambda).Lambda(); // Preparing arguments, if there are any. if (input.Children.Any()) { lambda.Insert(0, new Node(".arguments", null, input.Children.ToList())); } lambda.Insert(0, new Node(".filename", filename)); // Evaluating lambda of slot. await signaler.SignalAsync("eval", lambda); // Clearing Children collection, since it might contain input parameters. input.Clear(); /* * Simply setting value and children to "slots.result" stack object, since its value * was initially set to its old value during startup of method. */ input.Value = result.Value; input.AddRange(result.Children.ToList()); }); }
/// <summary> /// Implementation of signal /// </summary> /// <param name="signaler">Signaler used to signal</param> /// <param name="input">Parameters passed from signaler</param> /// <returns>An awaiatble task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { var arguments = GetArguments(input); await signaler.ScopeAsync(arguments.Name, arguments.Value, async() => { await signaler.SignalAsync("eval", arguments.Lambda); }); }
/// <summary> /// Slot implementation. /// </summary> /// <param name="signaler">Signaler that raised signal.</param> /// <param name="input">Arguments to slot.</param> /// <result>Arguments to slot.</result> public async Task SignalAsync(ISignaler signaler, Node input) { // Making sure we're able to handle returned values and nodes from slot implementation. var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { // Evaluating lambda of slot, making sure we temporary clear any existing [whitelist] declarations. var lambda = GetLambda(signaler, input); await signaler.ScopeAsync("whitelist", null, async() => { await signaler.SignalAsync("eval", lambda); }); input.Clear(); input.Value = result.Value; input.AddRange(result.Children.ToList()); }); }
/// <summary> /// Handles the signal for the class. /// </summary> /// <param name="signaler">Signaler used to signal the slot.</param> /// <param name="input">Root node for invocation.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { using (var connection = new MySqlConnectionWrapper(GetConnectionString(input))) { await signaler.ScopeAsync( "mysql.connect", connection, async() => await signaler.SignalAsync("eval", input)); input.Value = null; } }
/// <summary> /// Slot implementation. /// </summary> /// <param name="signaler">Signaler that raised the signal.</param> /// <param name="input">Arguments to slot.</param> public async Task SignalAsync(ISignaler signaler, Node input) { var args = GetArgs(input); input.Value = await _cache.GetOrCreateAsync(args.Key, async() => { var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { await signaler.SignalAsync("eval", args.Lambda.Clone()); }); return(result.GetEx <string>(), args.UtcExpires); });
/// <summary> /// Implementation of your slot. /// </summary> /// <param name="signaler">Signaler used to raise the signal.</param> /// <param name="input">Arguments to your slot.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { using (var connection = new SqlConnectionWrapper( Executor.GetConnectionString( input, "mssql", "master", _configuration))) { await signaler.ScopeAsync( "mssql.connect", connection, async() => await signaler.SignalAsync("eval", input)); input.Value = null; } }
/// <summary> /// Slot implementation. /// </summary> /// <param name="signaler">Signaler that raised the signal.</param> /// <param name="input">Arguments to slot.</param> public async Task SignalAsync(ISignaler signaler, Node input) { var args = GetArgs(input); input.Value = await _cache.GetOrCreateAsync(args.Item1, async entry => { var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { await signaler.SignalAsync("eval", args.Item2.Clone()); }); ConfigureCacheObject(entry, input); return(result.Value ?? result.Clone()); }); input.Clear(); }
/// <summary> /// Implementation of slot. /// </summary> /// <param name="signaler">Signaler used to raise the signal.</param> /// <param name="input">Arguments to slot.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { // Making sure we're able to handle returned values and nodes from slot implementation. var result = new Node(); await signaler.ScopeAsync("slots.result", result, async() => { // Loading file and converting its content to Hyperlambda. var filename = input.GetEx <string>(); var hyperlambda = await _service.LoadAsync( PathResolver.CombinePaths( _rootResolver.RootFolder, filename)); // Creating and parametrising our lambda object from argument + file's Hyperlambda content. var lambda = GetLambda(input, hyperlambda, filename); // Evaluating lambda of slot. await signaler.SignalAsync("eval", lambda); // Applying result. ApplyResult(input, result); }); }
/* * Unrolls plugins referenced in specified content. */ async Task <string> UnrollPluginsAsync( ISignaler signaler, string content, Node node, bool isMarkdown) { var result = new StringBuilder(); var buffer = new StringBuilder(); using (var reader = new StringReader(content)) { var line = reader.ReadLine(); while (line != null) { // TODO: Implement true parsing, to allow for having plugins inside of e.g. attributes, etc. if (line == "![[") { /* * Hyperlambda content, reading until we find the end of it, parsing, * evaluating, and adding results of evaluation into buffer. * * Making sure we first append content found so far in StringReader. */ if (isMarkdown) { result.Append(Markdown.ToHtml(buffer.ToString())); } else { result.Append(buffer.ToString()); } buffer.Clear(); line = reader.ReadLine(); // Discarding opening "![[" parts. while (line != "]]!") { buffer.Append(line).Append("\r\n"); line = reader.ReadLine(); } /* * Notice, passing in all arguments as [.arguments] to all * inline Hyperlambda plugin sections. */ var lambda = new Parser(buffer.ToString()).Lambda(); lambda.Add(new Node(".arguments", null, node.Children.Select(x => x.Clone()))); buffer.Clear(); var evalResult = new Node(); await signaler.ScopeAsync( "slots.result", evalResult, async() => await signaler.SignalAsync("wait.eval", lambda)); /* * Assuming the plugin section returned some sort of inclusion, * effectively being server side "document.write" logic. */ result.Append(evalResult.Get <string>()); } else { // Normal content. buffer.Append(line).Append("\r\n"); } line = reader.ReadLine(); } if (isMarkdown) { result.Append(Markdown.ToHtml(buffer.ToString())); } else { result.Append(buffer.ToString()); } } return(result.ToString()); }
/// <summary> /// Handles the signal for the class. /// </summary> /// <param name="signaler">Signaler used to signal the slot.</param> /// <param name="input">Root node for invocation.</param> /// <returns>An awaitable task.</returns> public async Task SignalAsync(ISignaler signaler, Node input) { // Checking cache first. var key = input.GetEx <string>(); if (!GetFromCache(key, input)) { /* * Making sure no more than max number of threads can * execute code simultaneously. This is to avoid hundreds of * simultaneous threads trying to access the database, and/or the same * document at the same time, before cache has been validated, * resulting in exhausting the server. */ await _semaphore.WaitAsync(); try { // Double checking, in case another thread was able to retrieve content first. if (!GetFromCache(key, input)) { // Evaluating [.lambda] to retrieve item to cache and return to caller. var evalResult = new Node(); await signaler.ScopeAsync( "slots.result", evalResult, async() => await signaler.SignalAsync("wait.eval", input.Children.First(x => x.Name == ".lambda"))); /* * Checking to see if anything was returned at all, * and if not, we don't store anything into our cache. */ if (evalResult.Value != null || evalResult.Children.Any()) { /* * Storing into cache, notice we default cache time to 60 seconds, unless * explicitly overridden as [seconds]. * We also clone result before adding to cache, to prevent later changes to propagate * to cache value. */ var clone = evalResult.Clone(); _memoryCache.Set( key, clone, DateTimeOffset.Now.AddSeconds( input.Children.FirstOrDefault(x => x.Name == "seconds")?.GetEx <int>() ?? 60)); // Clearing children and value, and adding value fetched from lambda. input.Clear(); input.Value = null; input.AddRange(evalResult.Children.Select(x => x.Clone())); input.Value = evalResult.Value; } } } finally { _semaphore.Release(); } } }
/// <inheritdoc/> public async Task <MagicResponse> ExecuteAsync(MagicRequest request) { // Sanity checking invocation if (string.IsNullOrEmpty(request.URL)) { return new MagicResponse { Result = 404 } } ; // Making sure we never resolve to anything outside of "modules/" and "system/" folder. if (!request.URL.StartsWith("modules/") && !request.URL.StartsWith("system/")) { return new MagicResponse { Result = 401 } } ; // Figuring out file to execute, and doing some basic sanity checking. var path = Utilities.GetEndpointFilePath(_rootResolver, request.URL, request.Verb); if (!await _fileService.ExistsAsync(path)) { return new MagicResponse { Result = 404 } } ; // Creating our lambda object by loading Hyperlambda file. var lambda = HyperlambdaParser.Parse(await _fileService.LoadAsync(path)); // Applying interceptors. lambda = await ApplyInterceptors(lambda, request.URL); // Attaching arguments. _argumentsHandler.Attach(lambda, request.Query, request.Payload); // Invoking method responsible for actually executing lambda object. return(await ExecuteAsync(lambda, request)); } #region [ -- Private helper methods -- ] /* * Applies interceptors to specified Node/Lambda object. */ async Task <Node> ApplyInterceptors(Node result, string url) { // Checking to see if interceptors exists recursively upwards in folder hierarchy. var splits = url.Split(new char [] { '/' }, StringSplitOptions.RemoveEmptyEntries); // Stripping away last entity (filename) of invocation. var folders = splits.Take(splits.Length - 1); // Iterating as long as we have more entities in list of folders. while (true) { // Checking if "current-folder/interceptor.hl" file exists. var current = _rootResolver.AbsolutePath(string.Join("/", folders) + "/interceptor.hl"); if (_fileService.Exists(current)) { result = await ApplyInterceptor(result, current); } // Checking if we're done, and at root folder, at which point we break while loop. if (!folders.Any()) { break; // We're done, no more interceptors! } // Traversing upwards in hierarchy to be able to nest interceptors upwards in hierarchy. folders = folders.Take(folders.Count() - 1); } // Returning result to caller. return(result); } /* * Applies the specified interceptor and returns the transformed Node/Lambda result. */ async Task <Node> ApplyInterceptor(Node lambda, string interceptorFile) { // Getting interceptor lambda. var interceptNode = HyperlambdaParser.Parse(await _fileService.LoadAsync(interceptorFile)); // Moving [.arguments] from endpoint lambda to the top of interceptor lambda if existing. var args = lambda .Children .Where(x => x.Name == ".arguments" || x.Name == ".description" || x.Name == ".type" || x.Name == "auth.ticket.verify" || x.Name.StartsWith("validators.")); // Notice, reversing arguments nodes makes sure we apply arguments in order of appearance. foreach (var idx in args.Reverse().ToList()) { interceptNode.Insert(0, idx); // Notice, will detach the argument from its original position! } // Moving endpoint Lambda to position before any [.interceptor] node found in interceptor lambda. foreach (var idxLambda in new Expression("**/.interceptor").Evaluate(interceptNode).ToList()) { // Iterating through each node in current result and injecting before currently iterated [.lambda] node. foreach (var idx in lambda.Children) { // This logic ensures we keep existing order without any fuzz. // By cloning node we also support having multiple [.interceptor] nodes. idxLambda.InsertBefore(idx.Clone()); } // Removing currently iterated [.interceptor] node in interceptor lambda object. idxLambda.Parent.Remove(idxLambda); } // Returning interceptor Node/Lambda which is now the root of the execution Lambda object. return(interceptNode); } /* * Method responsible for actually executing lambda object after file has been loaded, * interceptors and arguments have been applied, and transforming result of invocation * to a MagicResponse. */ async Task <MagicResponse> ExecuteAsync(Node lambda, MagicRequest request) { // Creating our result wrapper, wrapping whatever the endpoint wants to return to the client. var result = new Node(); var response = new MagicResponse(); try { await _signaler.ScopeAsync("http.request", request, async() => { await _signaler.ScopeAsync("http.response", response, async() => { await _signaler.ScopeAsync("slots.result", result, async() => { await _signaler.SignalAsync("eval", lambda); }); }); }); response.Content = GetReturnValue(response, result); return(response); } catch { if (result.Value is IDisposable disposable) { disposable.Dispose(); } if (response.Content is IDisposable disposable2 && !object.ReferenceEquals(response.Content, result.Value)) { disposable2.Dispose(); } throw; } } /* * Creates a returned payload of some sort and returning to caller. */ object GetReturnValue(MagicResponse httpResponse, Node lambda) { /* * An endpoint can return either a Node/Lambda hierarchy or a simple value. * First we check if endpoint returned a simple value, at which point we convert it to * a string. Notice, we're prioritising simple values, implying if return node has a * simple value, none of its children nodes will be returned. */ if (lambda.Value != null) { // IDisposables (Streams e.g.) are automatically disposed by ASP.NET Core. if (lambda.Value is IDisposable || lambda.Value is byte[]) { return(lambda.Value); } return(lambda.Get <string>()); } else if (lambda.Children.Any()) { // Checking if we should return content as Hyperlambda. if (httpResponse.Headers.TryGetValue("Content-Type", out var val) && val == "application/x-hyperlambda") { return(HyperlambdaGenerator.GetHyperlambda(lambda.Children)); } // Defaulting to returning content as JSON by converting from Lambda to JSON. var convert = new Node(); convert.AddRange(lambda.Children.ToList()); _signaler.Signal(".lambda2json-raw", convert); return(convert.Value); } return(null); // No content } #endregion } }