Added basic logging, just a facade for now
Added FissionContext class Changed name on both class and function for input code to avoid namespace clashes when adding FissionContext Updated README Added section on how to develop/debug the code to README, might be obvious for some but not all Changed code input from static function to method
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
bin/*
|
||||
obj/*
|
||||
out/*
|
||||
.vscode/*
|
||||
project.lock*
|
||||
TODO_dotnet
|
||||
@@ -1,4 +1,5 @@
|
||||
using Fission.DotNetCore.Compiler;
|
||||
using Fission.DotNetCore.Api;
|
||||
using System.Collections.Generic;
|
||||
using Nancy;
|
||||
using System.IO;
|
||||
@@ -8,9 +9,13 @@ namespace Fission.DotNetCore
|
||||
{
|
||||
public class ExecutorModule : NancyModule
|
||||
{
|
||||
#if DEBUG
|
||||
private const string CODE_PATH = "/tmp/func.cs";
|
||||
#else
|
||||
private const string CODE_PATH = "/userfunc/user";
|
||||
|
||||
#endif
|
||||
private static Function _userFunc;
|
||||
private static Logger _logger = new Logger();
|
||||
|
||||
public ExecutorModule()
|
||||
{
|
||||
@@ -21,7 +26,6 @@ namespace Fission.DotNetCore
|
||||
Head("/", _ => Run());
|
||||
Options("/", _ => Run());
|
||||
Delete("/", _ => Run());
|
||||
|
||||
}
|
||||
|
||||
private object Specialize()
|
||||
@@ -34,7 +38,7 @@ namespace Fission.DotNetCore
|
||||
if (_userFunc == null)
|
||||
{
|
||||
var errstr = string.Join(Environment.NewLine, errors);
|
||||
Console.WriteLine(errstr);
|
||||
_logger.WriteError(errstr);
|
||||
var response = (Response)errstr;
|
||||
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||
return response;
|
||||
@@ -44,7 +48,9 @@ namespace Fission.DotNetCore
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = (Response)"Unable to locate code";
|
||||
var errstr = $"Unable to locate code at '{CODE_PATH}'";
|
||||
_logger.WriteError(errstr);
|
||||
var response = (Response)errstr;
|
||||
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||
return response;
|
||||
}
|
||||
@@ -59,7 +65,18 @@ namespace Fission.DotNetCore
|
||||
return response;
|
||||
}
|
||||
var args = ((DynamicDictionary)Request.Query).ToDictionary();
|
||||
return _userFunc.Invoke(args);
|
||||
try
|
||||
{
|
||||
return _userFunc.Invoke(new FissionContext(args, new Logger()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.WriteError(e.ToString());
|
||||
var response = (Response)e.Message;
|
||||
response.StatusCode = HttpStatusCode.BadRequest;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,31 +12,46 @@ namespace Fission.DotNetCore.Compiler
|
||||
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
|
||||
class FissionCompiler
|
||||
{
|
||||
public static Function Compile(string code, out List<string> errors) {
|
||||
public static Function Compile(string code, out List<string> errors)
|
||||
{
|
||||
errors = new List<string>();
|
||||
|
||||
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
|
||||
|
||||
|
||||
string assemblyName = Path.GetRandomFileName();
|
||||
MetadataReference[] references = new MetadataReference[]
|
||||
|
||||
var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location);
|
||||
|
||||
List<MetadataReference> references = new List<MetadataReference>
|
||||
{
|
||||
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location)
|
||||
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
|
||||
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
|
||||
MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location)
|
||||
};
|
||||
|
||||
foreach (var referencedAssembly in Assembly.GetEntryAssembly().GetReferencedAssemblies())
|
||||
{
|
||||
var assembly = Assembly.Load(referencedAssembly);
|
||||
references.Add(MetadataReference.CreateFromFile(assembly.Location));
|
||||
}
|
||||
|
||||
CSharpCompilation compilation = CSharpCompilation.Create(
|
||||
assemblyName,
|
||||
syntaxTrees: new[] { syntaxTree },
|
||||
references: references,
|
||||
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
options: new CSharpCompilationOptions(
|
||||
OutputKind.DynamicallyLinkedLibrary,
|
||||
optimizationLevel: OptimizationLevel.Release));
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
EmitResult result = compilation.Emit(ms);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
|
||||
diagnostic.IsWarningAsError ||
|
||||
diagnostic.Severity == DiagnosticSeverity.Error);
|
||||
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
|
||||
diagnostic.IsWarningAsError ||
|
||||
diagnostic.Severity == DiagnosticSeverity.Error).ToList();
|
||||
|
||||
foreach (Diagnostic diagnostic in failures)
|
||||
{
|
||||
@@ -46,14 +61,14 @@ namespace Fission.DotNetCore.Compiler
|
||||
else
|
||||
{
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
|
||||
var type = assembly.GetType("Fission");
|
||||
var info = type.GetMember("Run").First() as MethodInfo;
|
||||
return new Function(info);
|
||||
var type = assembly.GetType("FissionFunction");
|
||||
var info = type.GetMember("Execute").First() as MethodInfo;
|
||||
return new Function(assembly, type, info);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Fission.DotNetCore.Api
|
||||
{
|
||||
public class FissionContext
|
||||
{
|
||||
public FissionContext(Dictionary<string, object> args, Logger logger)
|
||||
{
|
||||
if (args == null) throw new ArgumentNullException(nameof(args));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
Arguments = args;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Arguments { get; private set; }
|
||||
public Logger Logger { get; private set; }
|
||||
}
|
||||
|
||||
public class Logger
|
||||
{
|
||||
public void Write(Severity severity, string format, params object[] args)
|
||||
{
|
||||
Console.WriteLine($"{DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")} {severity}: " + format, args);
|
||||
}
|
||||
|
||||
public void WriteInfo(string format, params object[] args){
|
||||
Write(Severity.Info, format, args);
|
||||
}
|
||||
|
||||
public void WriteWarning(string format, params object[] args){
|
||||
Write(Severity.Warning, format, args);
|
||||
}
|
||||
|
||||
public void WriteError(string format, params object[] args){
|
||||
Write(Severity.Error, format, args);
|
||||
}
|
||||
|
||||
public void WriteCritical(string format, params object[] args){
|
||||
Write(Severity.Critical, format, args);
|
||||
}
|
||||
|
||||
public void WriteVerbose(string format, params object[] args){
|
||||
Write(Severity.Verbose, format, args);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Severity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical,
|
||||
Verbose
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,28 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using Fission.DotNetCore.Api;
|
||||
|
||||
namespace Fission.DotNetCore.Compiler
|
||||
{
|
||||
class Function
|
||||
{
|
||||
private readonly Assembly _assembly;
|
||||
private readonly Type _type;
|
||||
private readonly MethodInfo _info;
|
||||
public Function(MethodInfo info)
|
||||
|
||||
public Function(Assembly assembly, Type type, MethodInfo info)
|
||||
{
|
||||
if (info == null) throw new ArgumentNullException(nameof(info));
|
||||
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
|
||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||
_assembly = assembly;
|
||||
_type = type;
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public object Invoke(Dictionary<string, object> args)
|
||||
public object Invoke(FissionContext context)
|
||||
{
|
||||
return _info.Invoke(null, new[] { args });
|
||||
return _info.Invoke(_assembly.CreateInstance(_type.FullName), new[] { context });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,9 @@ namespace Fission.DotNetCore
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseKestrel()
|
||||
.UseUrls("http://*:8888")
|
||||
.UseStartup<Startup>()
|
||||
.Configure(app => app.UseOwin(x => x.UseNancy()))
|
||||
.Build();
|
||||
host.Run();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseOwin(x => x.UseNancy());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,22 @@ One workaround for this would be to add the references to this project's
|
||||
project.json file and rebuild the container.
|
||||
|
||||
The environment works via convention where you create a C# class
|
||||
called Fission which has a static method named Run taking a single
|
||||
parameter, a dictionary containing any querystring parameters.
|
||||
called FissionFunction which has a method named Execute taking a single
|
||||
parameter, a FissionContext object.
|
||||
|
||||
The FissionContext object gives access to the arguments and other items
|
||||
like logging. Please see FissionContext.cs for public API.
|
||||
|
||||
Example of simplest possible class to be executed:
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fission.DotNetCore.Api;
|
||||
|
||||
public class Fission {
|
||||
public static string Run(Dictionary<string, object> args) {
|
||||
return null;
|
||||
}
|
||||
public class FissionFunction {
|
||||
public string Execute(FissionContext context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -96,12 +99,14 @@ Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fission.DotNetCore.Api;
|
||||
|
||||
public class Fission {
|
||||
public static string Run(Dictionary<string, object> args) {
|
||||
return (string)args["text"];
|
||||
}
|
||||
public class FissionFunction
|
||||
{
|
||||
public string Execute(FissionContext context){
|
||||
context.Logger.WriteInfo("executing.. {0}", context.Arguments["text"]);
|
||||
return (string)context.Arguments["text"];
|
||||
}
|
||||
}
|
||||
```
|
||||
### Run the example
|
||||
@@ -132,14 +137,15 @@ Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||
|
||||
```
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fission.DotNetCore.Api;
|
||||
|
||||
public class Fission {
|
||||
public static string Run(Dictionary<string, object> args) {
|
||||
var x = Convert.ToInt32(args["x"]);
|
||||
var y = Convert.ToInt32(args["y"]);
|
||||
public class FissionFunction
|
||||
{
|
||||
public string Execute(FissionContext context){
|
||||
var x = Convert.ToInt32(context.Arguments["x"]);
|
||||
var y = Convert.ToInt32(context.Arguments["y"]);
|
||||
return (x+y).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
### Run the example
|
||||
@@ -156,3 +162,37 @@ $ fission route create --method GET --url /add --function addition
|
||||
$ curl "http://$FISSION_ROUTER/add?x=30&y=12"
|
||||
42
|
||||
```
|
||||
|
||||
## Developing/debugging the enviroment locally
|
||||
|
||||
The easiest way to debug the environment is to open the directory in
|
||||
Visual Studio Code (VSCode) as that will setup debugger for you the
|
||||
first time.
|
||||
|
||||
Remember to install the excellent extension
|
||||
"C# for Visual Studio Code(powered by OmniSharp)" to get statement completion
|
||||
|
||||
The class ExecutorModule contain preprocessor directive overriding where
|
||||
the input code file should be found:
|
||||
|
||||
```
|
||||
#if DEBUG
|
||||
private const string CODE_PATH = "/tmp/func.cs";
|
||||
#else
|
||||
private const string CODE_PATH = "/userfunc/user";
|
||||
#endif
|
||||
```
|
||||
|
||||
So what you need to do is:
|
||||
1. Open the directory in VSCode.
|
||||
This will prompt restore of packages and query is debugger setup is needed. Accept both prompts.
|
||||
2. Press F5 to start the web server. Set breakpoints etc..
|
||||
3. Add a code file containing valid C# at /tmp/func.cs
|
||||
4. Specialize the service with curl via post
|
||||
```
|
||||
$ curl -XPOST http://localhost:8888/specialize
|
||||
```
|
||||
5. Call your function with curl
|
||||
```
|
||||
$ curl -XGET http://localhost:8888
|
||||
```
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
dotnet restore
|
||||
dotnet publish -c Release -o out
|
||||
docker build -t fission/dotnet-env .
|
||||
docker build -t fission/dotnet-env .
|
||||
|
||||
Reference in New Issue
Block a user