From f3c3e29ef789b6453dcf587ce0249de1582cab8b Mon Sep 17 00:00:00 2001 From: Guillermo Leon <18090460+gleono@users.noreply.github.com> Date: Tue, 24 Sep 2024 21:11:33 -0500 Subject: [PATCH 1/5] Enable nullable type references --- .../Apache.OpenWhisk.Runtime.Common.csproj | 1 + .../Apache.OpenWhisk.Runtime.Common/Init.cs | 68 ++++++++++--------- .../Apache.OpenWhisk.Runtime.Common/Run.cs | 26 +++---- .../Startup.cs | 8 +-- ...he.OpenWhisk.Runtime.Dotnet.Minimal.csproj | 1 + 5 files changed, 54 insertions(+), 50 deletions(-) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj index 023908e..b1e7865 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Apache.OpenWhisk.Runtime.Common.csproj @@ -18,6 +18,7 @@ net6.0 + enable diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs index 0a1a054..1f25850 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -32,9 +32,9 @@ public class Init private readonly SemaphoreSlim _initSemaphoreSlim = new SemaphoreSlim(1, 1); public bool Initialized { get; private set; } - private Type Type { get; set; } - private MethodInfo Method { get; set; } - private ConstructorInfo Constructor { get; set; } + private Type? Type { get; set; } + private MethodInfo? Method { get; set; } + private ConstructorInfo? Constructor { get; set; } private bool AwaitableMethod { get; set; } public Init() @@ -45,7 +45,7 @@ public Init() Constructor = null; } - public async Task HandleRequest(HttpContext httpContext) + public async Task HandleRequest(HttpContext httpContext) { await _initSemaphoreSlim.WaitAsync(); try @@ -54,47 +54,46 @@ public async Task HandleRequest(HttpContext httpContext) { await httpContext.Response.WriteError("Cannot initialize the action more than once."); Console.Error.WriteLine("Cannot initialize the action more than once."); - return (new Run(Type, Method, Constructor, AwaitableMethod)); + return new Run(Type, Method, Constructor, AwaitableMethod); } string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); JObject inputObject = JObject.Parse(body); - if (!inputObject.ContainsKey("value")) + JToken? message; + if (!inputObject.TryGetValue("value", out message) || message is not JObject) { await httpContext.Response.WriteError("Missing main/no code to execute."); - return (null); + return null; } - JToken message = inputObject["value"]; + JObject valueObj = (JObject)message; + string main = valueObj.TryGetValue("main", out var mainToken) ? mainToken.ToString() : string.Empty; + string code = valueObj.TryGetValue("code", out var codeToken) ? codeToken.ToString() : string.Empty; + bool binary = valueObj.TryGetValue("binary", out var binaryToken) && binaryToken.ToObject(); - if (message["main"] == null || message["binary"] == null || message["code"] == null) + if (string.IsNullOrWhiteSpace(main) || string.IsNullOrWhiteSpace(code)) { await httpContext.Response.WriteError("Missing main/no code to execute."); - return (null); + return null; } - string main = message["main"].ToString(); - - bool binary = message["binary"].ToObject(); - if (!binary) { await httpContext.Response.WriteError("code must be binary (zip file)."); - return (null); + return null; } string[] mainParts = main.Split("::"); if (mainParts.Length != 3) { await httpContext.Response.WriteError("main required format is \"Assembly::Type::Function\"."); - return (null); + return null; } string tempPath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString()); - string base64Zip = message["code"].ToString(); try { - using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(base64Zip))) + using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(code))) { using (ZipArchive archive = new ZipArchive(stream)) { @@ -105,7 +104,7 @@ public async Task HandleRequest(HttpContext httpContext) catch (Exception) { await httpContext.Response.WriteError("Unable to decompress package."); - return (null); + return null; } Environment.CurrentDirectory = tempPath; @@ -117,19 +116,22 @@ public async Task HandleRequest(HttpContext httpContext) if (!File.Exists(assemblyPath)) { await httpContext.Response.WriteError($"Unable to locate requested assembly (\"{assemblyFile}\")."); - return (null); + return null; } try { + JToken? envToken = valueObj["env"]; // Export init arguments as environment variables - if (message["env"] != null && message["env"].HasValues) + if (envToken?.HasValues ?? false) { - Dictionary dictEnv = message["env"].ToObject>(); - foreach (KeyValuePair entry in dictEnv) { - // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable - // If entry.Value is null or the empty string, the variable is not set - Environment.SetEnvironmentVariable(entry.Key, entry.Value); + var dictEnv = envToken.ToObject>(); + if (dictEnv != null) { + foreach (var (key, value) in dictEnv) { + // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable + // If entry.Value is null or the empty string, the variable is not set + Environment.SetEnvironmentVariable(key, value); + } } } @@ -138,7 +140,7 @@ public async Task HandleRequest(HttpContext httpContext) if (Type == null) { await httpContext.Response.WriteError($"Unable to locate requested type (\"{mainParts[1]}\")."); - return (null); + return null; } Method = Type.GetMethod(mainParts[2]); Constructor = Type.GetConstructor(Type.EmptyTypes); @@ -151,26 +153,26 @@ await httpContext.Response.WriteError(ex.Message + ", " + ex.StackTrace #endif ); - return (null); + return null; } if (Method == null) { await httpContext.Response.WriteError($"Unable to locate requested method (\"{mainParts[2]}\")."); - return (null); + return null; } if (Constructor == null) { await httpContext.Response.WriteError($"Unable to locate appropriate constructor for (\"{mainParts[1]}\")."); - return (null); + return null; } Initialized = true; - AwaitableMethod = (Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null); + AwaitableMethod = Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; - return (new Run(Type, Method, Constructor, AwaitableMethod)); + return new Run(Type, Method, Constructor, AwaitableMethod); } catch (Exception ex) { @@ -181,7 +183,7 @@ await httpContext.Response.WriteError(ex.Message #endif ); Startup.WriteLogMarkers(); - return (null); + return null; } finally { diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs index 0ee99ce..91edd3f 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs @@ -26,12 +26,12 @@ namespace Apache.OpenWhisk.Runtime.Common { public class Run { - private readonly Type _type; - private readonly MethodInfo _method; - private readonly ConstructorInfo _constructor; + private readonly Type? _type; + private readonly MethodInfo? _method; + private readonly ConstructorInfo? _constructor; private readonly bool _awaitableMethod; - public Run(Type type, MethodInfo method, ConstructorInfo constructor, bool awaitableMethod) + public Run(Type? type, MethodInfo? method, ConstructorInfo? constructor, bool awaitableMethod) { _type = type; _method = method; @@ -51,10 +51,10 @@ public async Task HandleRequest(HttpContext httpContext) { string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); - JObject inputObject = string.IsNullOrEmpty(body) ? null : JObject.Parse(body); + JObject? inputObject = string.IsNullOrEmpty(body) ? null : JObject.Parse(body); - JObject valObject = null; - JArray valArray = null; + JObject? valObject = null; + JArray? valArray = null; if (inputObject != null) { @@ -66,7 +66,7 @@ public async Task HandleRequest(HttpContext httpContext) if (token.Path.Equals("value", StringComparison.InvariantCultureIgnoreCase)) continue; string envKey = $"__OW_{token.Path.ToUpperInvariant()}"; - string envVal = token.First.ToString(); + string? envVal = token.First?.ToString(); Environment.SetEnvironmentVariable(envKey, envVal); //Console.WriteLine($"Set environment variable \"{envKey}\" to \"{envVal}\"."); } @@ -85,20 +85,20 @@ await Console.Error.WriteLineAsync( try { - JContainer output; + JContainer? output; if(_awaitableMethod) { if (valObject != null) { - output = (JContainer) await (dynamic) _method.Invoke(owObject, new object[] {valObject}); + output = (JContainer?) await (dynamic?) _method.Invoke(owObject, new object?[] {valObject}); } else { - output = (JContainer) await (dynamic) _method.Invoke(owObject, new object[] {valArray}); + output = (JContainer?) await (dynamic?) _method.Invoke(owObject, new object?[] {valArray}); } } else { if (valObject != null) { - output = (JContainer) _method.Invoke(owObject, new object[] {valObject}); + output = (JContainer?) _method.Invoke(owObject, new object?[] {valObject}); } else { - output = (JContainer) _method.Invoke(owObject, new object[] {valArray}); + output = (JContainer?) _method.Invoke(owObject, new object?[] {valArray}); } } diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs index 9851e54..e8848bb 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Startup.cs @@ -31,10 +31,10 @@ public static void WriteLogMarkers() public void Configure(IApplicationBuilder app) { - PathString initPath = new PathString("/init"); - PathString runPath = new PathString("/run"); - Init init = new Init(); - Run run = null; + PathString initPath = new("/init"); + PathString runPath = new("/run"); + Init init = new(); + Run? run = null; app.Run(async (httpContext) => { if (httpContext.Request.Path.Equals(initPath)) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj index 1bcbb35..49af8c7 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Dotnet.Minimal/Apache.OpenWhisk.Runtime.Dotnet.Minimal.csproj @@ -19,6 +19,7 @@ Exe net6.0 + enable From cd42126d87cf4262bc845e8802e6b117bed2ed34 Mon Sep 17 00:00:00 2001 From: Guillermo Leon <18090460+gleono@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:48:20 -0500 Subject: [PATCH 2/5] Improve setting env variables on init --- .../Apache.OpenWhisk.Runtime.Common/Init.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs index 1f25850..c7cf5be 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -21,7 +21,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Newtonsoft.Json.Linq; @@ -67,9 +66,9 @@ public Init() } JObject valueObj = (JObject)message; - string main = valueObj.TryGetValue("main", out var mainToken) ? mainToken.ToString() : string.Empty; - string code = valueObj.TryGetValue("code", out var codeToken) ? codeToken.ToString() : string.Empty; - bool binary = valueObj.TryGetValue("binary", out var binaryToken) && binaryToken.ToObject(); + string main = valueObj.TryGetValue("main", out JToken? mainToken) ? mainToken.ToString() : string.Empty; + string code = valueObj.TryGetValue("code", out JToken? codeToken) ? codeToken.ToString() : string.Empty; + bool binary = valueObj.TryGetValue("binary", out JToken? binaryToken) && binaryToken.ToObject(); if (string.IsNullOrWhiteSpace(main) || string.IsNullOrWhiteSpace(code)) { @@ -121,17 +120,13 @@ public Init() try { - JToken? envToken = valueObj["env"]; // Export init arguments as environment variables - if (envToken?.HasValues ?? false) + if (valueObj.TryGetValue("env", out JToken? value) && value is JObject dictEnv) { - var dictEnv = envToken.ToObject>(); - if (dictEnv != null) { - foreach (var (key, value) in dictEnv) { - // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable - // If entry.Value is null or the empty string, the variable is not set - Environment.SetEnvironmentVariable(key, value); - } + foreach ((string key, JToken? varValue) in dictEnv) { + // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable + // If entry.Value is null or the empty string, the variable is not set + Environment.SetEnvironmentVariable(key, varValue?.ToString()); } } From 08316bc293fa2ee9972e4b3d8f3f7f7bb53baf58 Mon Sep 17 00:00:00 2001 From: Guillermo Leon <18090460+gleono@users.noreply.github.com> Date: Fri, 4 Oct 2024 13:48:13 -0500 Subject: [PATCH 3/5] Suggestion --- core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs index c7cf5be..24012fe 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -58,8 +58,7 @@ public Init() string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); JObject inputObject = JObject.Parse(body); - JToken? message; - if (!inputObject.TryGetValue("value", out message) || message is not JObject) + if (!inputObject.TryGetValue("value", out JToken? message) || message is not JObject) { await httpContext.Response.WriteError("Missing main/no code to execute."); return null; From 745eecb44641644fffbed908f72e40dd6504316f Mon Sep 17 00:00:00 2001 From: Guillermo Leon <18090460+gleono@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:34:10 -0500 Subject: [PATCH 4/5] Small new features of C# --- .../HttpResponseExtension.cs | 5 +- .../Apache.OpenWhisk.Runtime.Common/Init.cs | 50 +++++++++---------- .../Apache.OpenWhisk.Runtime.Common/Run.cs | 5 +- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs index 4f63bd0..8f97628 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/HttpResponseExtension.cs @@ -27,15 +27,14 @@ public static class HttpResponseExtension { public static async Task WriteResponse(this HttpResponse response, int code, string content) { - byte[] bytes = Encoding.UTF8.GetBytes(content); - response.ContentLength = bytes.Length; + response.ContentLength = Encoding.UTF8.GetByteCount(content); response.StatusCode = code; await response.WriteAsync(content); } public static async Task WriteError(this HttpResponse response, string errorMessage) { - JObject message = new JObject {{"error", new JValue(errorMessage)}}; + JObject message = new() {{"error", new JValue(errorMessage)}}; await WriteResponse(response, 502, JsonConvert.SerializeObject(message)); } diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs index 24012fe..fbd7628 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -28,20 +28,20 @@ namespace Apache.OpenWhisk.Runtime.Common { public class Init { - private readonly SemaphoreSlim _initSemaphoreSlim = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _initSemaphoreSlim = new(initialCount: 1, maxCount: 1); + private Type? _type; + private MethodInfo? _method; + private ConstructorInfo? _constructor; + private bool _awaitableMethod; public bool Initialized { get; private set; } - private Type? Type { get; set; } - private MethodInfo? Method { get; set; } - private ConstructorInfo? Constructor { get; set; } - private bool AwaitableMethod { get; set; } public Init() { Initialized = false; - Type = null; - Method = null; - Constructor = null; + _type = null; + _method = null; + _constructor = null; } public async Task HandleRequest(HttpContext httpContext) @@ -53,18 +53,18 @@ public Init() { await httpContext.Response.WriteError("Cannot initialize the action more than once."); Console.Error.WriteLine("Cannot initialize the action more than once."); - return new Run(Type, Method, Constructor, AwaitableMethod); + return new Run(_type, _method, _constructor, _awaitableMethod); } - string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); + using StreamReader reader = new(httpContext.Request.Body); + string body = await reader.ReadToEndAsync(); JObject inputObject = JObject.Parse(body); - if (!inputObject.TryGetValue("value", out JToken? message) || message is not JObject) + if (!inputObject.TryGetValue("value", out JToken? message) || message is not JObject valueObj) { await httpContext.Response.WriteError("Missing main/no code to execute."); return null; } - JObject valueObj = (JObject)message; string main = valueObj.TryGetValue("main", out JToken? mainToken) ? mainToken.ToString() : string.Empty; string code = valueObj.TryGetValue("code", out JToken? codeToken) ? codeToken.ToString() : string.Empty; bool binary = valueObj.TryGetValue("binary", out JToken? binaryToken) && binaryToken.ToObject(); @@ -91,13 +91,9 @@ public Init() string tempPath = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString()); try { - using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(code))) - { - using (ZipArchive archive = new ZipArchive(stream)) - { - archive.ExtractToDirectory(tempPath); - } - } + using MemoryStream stream = new(Convert.FromBase64String(code)); + using ZipArchive archive = new(stream); + archive.ExtractToDirectory(tempPath); } catch (Exception) { @@ -130,14 +126,14 @@ public Init() } Assembly assembly = Assembly.LoadFrom(assemblyPath); - Type = assembly.GetType(mainParts[1]); - if (Type == null) + _type = assembly.GetType(mainParts[1]); + if (_type == null) { await httpContext.Response.WriteError($"Unable to locate requested type (\"{mainParts[1]}\")."); return null; } - Method = Type.GetMethod(mainParts[2]); - Constructor = Type.GetConstructor(Type.EmptyTypes); + _method = _type.GetMethod(mainParts[2]); + _constructor = _type.GetConstructor(Type.EmptyTypes); } catch (Exception ex) { @@ -150,13 +146,13 @@ await httpContext.Response.WriteError(ex.Message return null; } - if (Method == null) + if (_method == null) { await httpContext.Response.WriteError($"Unable to locate requested method (\"{mainParts[2]}\")."); return null; } - if (Constructor == null) + if (_constructor == null) { await httpContext.Response.WriteError($"Unable to locate appropriate constructor for (\"{mainParts[1]}\")."); return null; @@ -164,9 +160,9 @@ await httpContext.Response.WriteError(ex.Message Initialized = true; - AwaitableMethod = Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; + _awaitableMethod = _method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null; - return new Run(Type, Method, Constructor, AwaitableMethod); + return new Run(_type, _method, _constructor, _awaitableMethod); } catch (Exception ex) { diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs index 91edd3f..13e1eeb 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Run.cs @@ -49,7 +49,8 @@ public async Task HandleRequest(HttpContext httpContext) try { - string body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); + using StreamReader reader = new(httpContext.Request.Body); + string body = await reader.ReadToEndAsync(); JObject? inputObject = string.IsNullOrEmpty(body) ? null : JObject.Parse(body); @@ -81,7 +82,7 @@ await Console.Error.WriteLineAsync( } } - object owObject = _constructor.Invoke(new object[] { }); + object owObject = _constructor.Invoke(Array.Empty()); try { From 695e15baf1ac8bc865c262eee30cd563c7f02073 Mon Sep 17 00:00:00 2001 From: Guillermo Leon <18090460+gleono@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:54:29 -0500 Subject: [PATCH 5/5] Updated comment and variable names --- core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs index fbd7628..56f84bf 100644 --- a/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs +++ b/core/net6.0/proxy/Apache.OpenWhisk.Runtime.Common/Init.cs @@ -116,12 +116,12 @@ public Init() try { // Export init arguments as environment variables - if (valueObj.TryGetValue("env", out JToken? value) && value is JObject dictEnv) + if (valueObj.TryGetValue("env", out JToken? values) && values is JObject dictEnv) { - foreach ((string key, JToken? varValue) in dictEnv) { + foreach ((string envKey, JToken? envVal) in dictEnv) { // See https://docs.microsoft.com/en-us/dotnet/api/system.environment.setenvironmentvariable - // If entry.Value is null or the empty string, the variable is not set - Environment.SetEnvironmentVariable(key, varValue?.ToString()); + // If envVal is null or the empty string, the variable is not set + Environment.SetEnvironmentVariable(envKey, envVal?.ToString()); } }