diff --git a/SharpBoss.Logging/Logger.cs b/SharpBoss.Logging/Logger.cs index 4862ff5..51c04a1 100644 --- a/SharpBoss.Logging/Logger.cs +++ b/SharpBoss.Logging/Logger.cs @@ -6,110 +6,127 @@ using NLog.Config; using NLog.Targets; -namespace SharpBoss.Logging { - /// - /// Logger class - /// - public static class Logger { - private static readonly string _datePattern = "yyyy-MM-dd"; - private static readonly string _filenamePattern = "sharpboss_{0}.txt"; - private static readonly string _environmentVariable = "SHARPBOSS_CONFIG_FILENAME"; - private static readonly string _configKey = "SHARPBOSS_CONFIG_FILENAME"; - - /// - /// Retrieve Logger - /// - /// ILogger - private static ILogger GetLogger () { - var stackFrame = new StackFrame (2, true); - var method = stackFrame.GetMethod (); - var assembly = method.DeclaringType; - - LogManager.Configuration = GetConfig (); - - var loggingName = string.Format ("{0}::{1}", assembly.FullName, method.Name); - - return LogManager.GetLogger (loggingName); - } - - /// - /// Retrieve filename for Logging output - /// - /// Retrieve filename for Logging output - private static string GetFileName () { - var dateTime = DateTime.Now; - var filename = string.Format (_filenamePattern, dateTime.ToString (_datePattern)); - var environmentVariable = Environment.GetEnvironmentVariable (_environmentVariable); - var configValue = ConfigurationManager.AppSettings[_configKey]; - - if (environmentVariable != null) { - return environmentVariable; - } else if (configValue != null) { - return configValue; - } - - return filename; - } - - /// - /// Get configuration for Log target - /// - /// NLog Configuration - private static LoggingConfiguration GetConfig () { - var config = new LoggingConfiguration (); - var target = new FileTarget { - FileName = GetFileName (), - Layout = "${longdate} ${level:lowercase=true} [${logger}] ${message}", - }; - - config.AddRuleForAllLevels (target); - - return config; - } - - /// - /// Retrieve message with format - /// - /// Message to log - /// Formatted message - private static string GetMessage (string message) { - return string.Format ("{0}", message); - } - - /// - /// Log message with info level - /// - /// Message to log - public static void Info (string message) { - var logger = GetLogger (); - logger.Info (GetMessage (message)); - } - - /// - /// Log message with debug level - /// - /// Message to log - public static void Debug (string message) { - var logger = GetLogger (); - logger.Debug (GetMessage (message)); - } - - /// - /// Log message with warning level - /// - /// Message to log - public static void Warn (string message) { - var logger = GetLogger (); - logger.Warn (GetMessage (message)); - } - +namespace SharpBoss.Logging +{ /// - /// Log message with error level + /// Logger class /// - /// Message to log - public static void Error (string message) { - var logger = GetLogger (); - logger.Error (GetMessage (message)); + public static class Logger + { + private static readonly string _datePattern = "yyyy-MM-dd"; + private static readonly string _filenamePattern = "sharpboss_{0}.txt"; + private static readonly string _environmentVariable = "SHARPBOSS_CONFIG_FILENAME"; + private static readonly string _configKey = "SHARPBOSS_CONFIG_FILENAME"; + + static readonly ILogger Log = LogManager.GetCurrentClassLogger(); + + /// + /// Retrieve Logger + /// + /// ILogger + private static ILogger GetLogger() + { + //var stackFrame = new StackFrame (2, true); + //var method = stackFrame.GetMethod (); + //var assembly = method.DeclaringType; + + //LogManager.Configuration = GetConfig (); + + //var loggingName = string.Format ("{0}::{1}", assembly.FullName, method.Name); + + //return LogManager.GetLogger (loggingName); + return Log; + } + + /// + /// Retrieve filename for Logging output + /// + /// Retrieve filename for Logging output + private static string GetFileName() + { + var dateTime = DateTime.Now; + var filename = string.Format(_filenamePattern, dateTime.ToString(_datePattern)); + var environmentVariable = Environment.GetEnvironmentVariable(_environmentVariable); + var configValue = ConfigurationManager.AppSettings[_configKey]; + + if (environmentVariable != null) + { + return environmentVariable; + } + else if (configValue != null) + { + return configValue; + } + + return filename; + } + + /// + /// Get configuration for Log target + /// + /// NLog Configuration + private static LoggingConfiguration GetConfig() + { + var config = new LoggingConfiguration(); + var target = new FileTarget + { + FileName = GetFileName(), + Layout = "${longdate} ${level:lowercase=true} [${logger}] ${message}", + }; + + config.AddRuleForAllLevels(target); + + return config; + } + + /// + /// Retrieve message with format + /// + /// Message to log + /// Formatted message + private static string GetMessage(string message) + { + return string.Format("{0}", message); + } + + /// + /// Log message with info level + /// + /// Message to log + public static void Info(string message) + { + var logger = GetLogger(); + logger.Info(GetMessage(message)); + } + + /// + /// Log message with debug level + /// + /// Message to log + public static void Debug(string message) + { + var logger = GetLogger(); + logger.Debug(GetMessage(message)); + } + + /// + /// Log message with warning level + /// + /// Message to log + public static void Warn(string message) + { + var logger = GetLogger(); + logger.Warn(GetMessage(message)); + } + + /// + /// Log message with error level + /// + /// Message to log + public static void Error(string message) + { + var logger = GetLogger(); + logger.Error(GetMessage(message)); + } } - } } \ No newline at end of file diff --git a/SharpBoss.Logging/SharpBoss.Logging.csproj b/SharpBoss.Logging/SharpBoss.Logging.csproj index adc366a..e937358 100644 --- a/SharpBoss.Logging/SharpBoss.Logging.csproj +++ b/SharpBoss.Logging/SharpBoss.Logging.csproj @@ -1,7 +1,11 @@ - netcoreapp3.1 + net48 + + + + x64 diff --git a/SharpBoss.Tests/SharpBoss.Tests.csproj b/SharpBoss.Tests/SharpBoss.Tests.csproj index b0b298a..a6d311b 100644 --- a/SharpBoss.Tests/SharpBoss.Tests.csproj +++ b/SharpBoss.Tests/SharpBoss.Tests.csproj @@ -1,11 +1,15 @@ - netcoreapp3.1 + net48 false + + x64 + + diff --git a/SharpBoss/Models/RestRequest.cs b/SharpBoss/Models/RestRequest.cs index 76de951..536113e 100644 --- a/SharpBoss/Models/RestRequest.cs +++ b/SharpBoss/Models/RestRequest.cs @@ -1,4 +1,5 @@ -using System; +using EmbedIO; +using System; using System.Collections.Specialized; using System.IO; using System.Net; @@ -131,6 +132,40 @@ public class RestRequest { /// public Uri UrlReferrer { get { return this._urlReferrer; } } + public RestRequest(IHttpContext context) + { + this._userAgent = context.Request.UserAgent; // request.UserAgent; + this._userHostAddress = context.Request.RemoteEndPoint.Address.ToString(); + this._userHostName = context.Request.RemoteEndPoint.Address.ToString(); + //this._userLanguages = context.Request.UserLanguages; + this._url = context.Request.Url; + this._urlReferrer = context.Request.UrlReferrer; + //this._acceptTypes = context.Request.AcceptTypes; + this._contentEncoding = context.Request.ContentEncoding; + this._contentLength = context.Request.ContentLength64; + this._contentType = context.Request.ContentType; + //this._cookies = context.Request.Cookies; + this._httpMethod = context.Request.HttpMethod; + this._isAuthenticated = context.Request.IsAuthenticated; + this._isLocal = context.Request.IsLocal; + this._isSecureConnection = context.Request.IsSecureConnection; + this._isWebSocketRequest = context.Request.IsWebSocketRequest; + this._keepAlive = context.Request.KeepAlive; + this._queryString = context.Request.QueryString; + this._rawUrl = context.Request.RawUrl; + + if (context.Request.HasEntityBody) + { + using (var body = context.Request.InputStream) + { + using (var reader = new StreamReader(body, context.Request.ContentEncoding)) + { + this._body = reader.ReadToEnd(); + } + } + } + } + /// /// Instantiate a new request with listener /// diff --git a/SharpBoss/Processors/RestProcessor.cs b/SharpBoss/Processors/RestProcessor.cs index 38ab119..78e2fe7 100644 --- a/SharpBoss/Processors/RestProcessor.cs +++ b/SharpBoss/Processors/RestProcessor.cs @@ -4,7 +4,7 @@ using System.Net; using System.Reflection; using System.Text.Json; - +using System.Text.Json.Serialization; using SharpBoss.Attributes; using SharpBoss.Attributes.Methods; using SharpBoss.Exceptions; @@ -25,6 +25,7 @@ public class RestProcessor { private Dictionary _proxies; private Dictionary _exceptionHandlers; private Dictionary _injectables; + private JsonSerializerOptions _serializerOptions; /// /// Create new REST processor @@ -34,6 +35,7 @@ public RestProcessor () { this._proxies = new Dictionary (); this._exceptionHandlers = new Dictionary (); this._injectables = new Dictionary (); + this._serializerOptions = new JsonSerializerOptions() { WriteIndented = true }; } /// @@ -49,6 +51,12 @@ public void Init (Assembly runningAssembly, string nameSpace) { foreach (var type in types) { var restAttribute = type.GetCustomAttribute (typeof (REST)); + if (typeof(JsonConverter<>).IsAssignableFrom(type) || typeof(JsonConverter).IsAssignableFrom(type)) + { + Logger.Info($"Found JsonConverter {type.Name}"); + _serializerOptions.Converters.Add((JsonConverter)Activator.CreateInstance(type)); + } + if (restAttribute != null) { Logger.Info ("Found REST class " + type.Name); var rest = (REST)restAttribute; @@ -137,7 +145,7 @@ public RestResponse Process (string path, string method, RestRequest request) { if (response is string) { return new RestResponse ((string)response); } else { - return new RestResponse (JsonSerializer.Serialize (response), "application/json"); + return new RestResponse (JsonSerializer.Serialize (response, _serializerOptions), "application/json"); } } else { return new RestResponse ("Not found", "text/plain", HttpStatusCode.NotFound); diff --git a/SharpBoss/SharpBoss.cs b/SharpBoss/SharpBoss.cs index ce282de..36753fb 100644 --- a/SharpBoss/SharpBoss.cs +++ b/SharpBoss/SharpBoss.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Specialized; using System.Configuration; +using System.IO; using System.Net; +using System.Security.Policy; using System.Text; - +using System.Threading.Tasks; +using EmbedIO; using SharpBoss.Logging; using SharpBoss.Models; using SharpBoss.Workers; @@ -13,14 +16,14 @@ namespace SharpBoss { /// SharBoss main class /// public class SharpBoss { - HttpServer _server; + WebServer _wserver; ApplicationLoader _applicationLoader; dynamic _appSettings; private readonly string _defaultListenUrl = @"http://localhost:8080/"; private readonly string _environmentVariable = "SHARPBOSS_URL"; private readonly string _configKey = "SHARPBOSS_URL"; - + private string _listenURL = ""; /// /// SharpBoss initializer, the listen URL can be defined on: /// @@ -41,7 +44,27 @@ public SharpBoss (string listenUrl = null, dynamic appSettings = null) { this._applicationLoader = new ApplicationLoader (); listenUrl = listenUrl + (listenUrl.EndsWith ("/") ? "" : "/"); - this._server = new HttpServer (listenUrl, ProcessHttpCalls); + //this._server = new HttpServer (listenUrl, ProcessHttpCalls); + this._wserver = new WebServer(o => o.WithUrlPrefix(listenUrl).WithMode(HttpListenerMode.EmbedIO)); + this._wserver.WithAction(HttpVerbs.Any, RequestHandlerCallback); + this._listenURL = listenUrl; + } + + public void ForceReload() { + this._applicationLoader.ForceReload(); + } + async Task RequestHandlerCallback(IHttpContext context) + { + var ePath = context.Request.Url.AbsolutePath.Split( + new char[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries + ); + + var req = new RestRequest(context); + RestResponse r = Process(ePath, req); + context.Response.StatusCode = ((int)r.StatusCode); + context.Response.ContentType = r.ContentType; + context.Response.ContentLength64 = r.Result.Length; + context.Response.OutputStream.Write(r.Result, 0, r.Result.Length); } /// @@ -60,14 +83,18 @@ private void SetAppSettings (dynamic appSettings = null) { /// Do the trick, like a boss! /// public void Run () { - this._server.Run (); + Logger.Info("Running SharpBoss WebServer"); + this._wserver.RunAsync(); } /// /// If u did the trick, like a boss. So it's the end D: /// public void Stop () { - this._server.Stop (); + try + { + this._wserver.Listener.Stop(); + } catch(Exception e) { } } /// @@ -75,7 +102,7 @@ public void Stop () { /// /// Listen URL public string GetHttpServerListenUrl () { - return this._server.ListenUrl; + return this._listenURL; } /// @@ -89,42 +116,59 @@ RestResponse ProcessHttpCalls (HttpListenerRequest request) { ); var req = new RestRequest (request); + return Process(ePath, req); + } - if (ePath.Length == 0) { - return new RestResponse ("No such endpoint.", "text/plain", HttpStatusCode.NotFound); - } else { + RestResponse Process(string[] ePath, RestRequest req) + { + + if (ePath.Length == 0) + { + return new RestResponse("No such endpoint.", "text/plain", HttpStatusCode.NotFound); + } + else + { var path = ePath.Length > 1 ? "/" + ePath[1] : "/"; - var method = request.HttpMethod; + var method = req.HttpMethod; var app = ePath[0]; - Logger.Debug (string.Format ("Received request for: APP={0} {1} {2}", app, method, path)); + Logger.Debug(string.Format("Received request for: APP={0} {1} {2}", app, method, path)); - if (this._applicationLoader.ContainsEndPoint (app, path, method)) { - try { - return this._applicationLoader.Process (app, path, method, req); - } catch (Exception ex) { - var response = new RestResponse (); + if (this._applicationLoader.ContainsEndPoint(app, path, method)) + { + try + { + return this._applicationLoader.Process(app, path, method, req); + } + catch (Exception ex) + { + var response = new RestResponse(); var exceptionMessage = ""; - if (ex.InnerException != null) { - exceptionMessage = ex.InnerException.ToString (); - } else { - exceptionMessage = ex.ToString (); + if (ex.InnerException != null) + { + exceptionMessage = ex.InnerException.ToString(); + } + else + { + exceptionMessage = ex.ToString(); } - Logger.Error (string.Format ( + Logger.Error(string.Format( "Exception when calling application {0} in endpoint {1} {2}\r\n{3}", app, method, path, exceptionMessage )); response.StatusCode = HttpStatusCode.InternalServerError; response.ContentType = "text/plain"; - response.Result = Encoding.UTF8.GetBytes (exceptionMessage); + response.Result = Encoding.UTF8.GetBytes(exceptionMessage); return response; } - } else { - return new RestResponse ("No such endpoint.", "text/plain", HttpStatusCode.NotFound); + } + else + { + return new RestResponse("No such endpoint.", "text/plain", HttpStatusCode.NotFound); } } } diff --git a/SharpBoss/SharpBoss.csproj b/SharpBoss/SharpBoss.csproj index fbd8079..bdd9ab0 100644 --- a/SharpBoss/SharpBoss.csproj +++ b/SharpBoss/SharpBoss.csproj @@ -1,17 +1,24 @@ - + - netcoreapp3.1 + net48 + + + + x64 - - - - - - - + + + + + + + + + + @@ -19,10 +26,13 @@ - + + + + diff --git a/SharpBoss/Workers/ApplicationLoader.cs b/SharpBoss/Workers/ApplicationLoader.cs index 116e35f..167e533 100644 --- a/SharpBoss/Workers/ApplicationLoader.cs +++ b/SharpBoss/Workers/ApplicationLoader.cs @@ -3,343 +3,404 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.Loader; +using System.Security.Policy; using System.Timers; using SharpBoss.Logging; using SharpBoss.Models; -namespace SharpBoss.Workers { - /// - /// - /// - internal class ApplicationLoader { - Timer _timer; - - Dictionary _appDomains; - Dictionary _loaderWorkers; - Dictionary _appActions; - - FileSystemWatcher _watcher; - - string _appsDir; - string _deployedDir; - - /// - /// Start the Rest watcher - /// - public ApplicationLoader () { - this._appDomains = new Dictionary (); - this._loaderWorkers = new Dictionary (); - this._watcher = new FileSystemWatcher (); - this._appActions = new Dictionary (); - this._appsDir = Path.Combine (".", "apps"); - this._deployedDir = Path.Combine (".", "deployed"); - - this.Init (); - - this._timer = new Timer (); - this._timer.Interval = 5 * 1000; - - this._watcher.Path = Path.Combine (".", "apps"); - Logger.Info ("Attaching FileSystemWatcher to " + this._watcher.Path); - - this._watcher.NotifyFilter = NotifyFilters.LastAccess - | NotifyFilters.LastWrite - | NotifyFilters.FileName - | NotifyFilters.DirectoryName; - - this._watcher.Filter = "*.*"; - this._watcher.IncludeSubdirectories = true; - this._watcher.EnableRaisingEvents = true; - } - - /// - /// Define all event handlers - /// - private void DefineEvents () { - this._timer.Elapsed += Timer_Elapsed; - this._watcher.Changed += new FileSystemEventHandler (onChanged); - this._watcher.Created += new FileSystemEventHandler (onCreated); - this._watcher.Deleted += new FileSystemEventHandler (onChanged); - this._watcher.Renamed += new RenamedEventHandler (onRenamed); - } - +namespace SharpBoss.Workers +{ /// - /// Event triggered after start a timer to update assembly + /// /// - /// Event source - /// Event arguments - private void Timer_Elapsed (object sender, ElapsedEventArgs e) { - Logger.Info ("AppTimer Elapsed. Checking for application changes."); - - var appActions = new List (); - - lock (this._appActions) { - var applicationNames = this._appActions.Keys.Select (x => x).ToArray (); - - foreach (var applicationName in applicationNames) { - appActions.Add (this._appActions[applicationName]); - this._appActions.Remove (applicationName); + internal class ApplicationLoader + { + Timer _timer; + + Dictionary _appDomains; + Dictionary _loaderWorkers; + Dictionary _appActions; + + FileSystemWatcher _watcher; + + string _appsDir; + string _deployedDir; + + /// + /// Start the Rest watcher + /// + public ApplicationLoader() + { + this._appDomains = new Dictionary(); + this._loaderWorkers = new Dictionary(); + this._watcher = new FileSystemWatcher(); + this._appActions = new Dictionary(); + this._appsDir = Path.Combine(".", "apps"); + this._deployedDir = Path.Combine(".", "deployed"); + + this.Init(); + + this._timer = new Timer(); + this._timer.Interval = 5 * 1000; + + this._watcher.Path = Path.Combine(".", "apps"); + Logger.Info("Attaching FileSystemWatcher to " + this._watcher.Path); + + this._watcher.NotifyFilter = NotifyFilters.LastAccess + | NotifyFilters.LastWrite + | NotifyFilters.FileName + | NotifyFilters.DirectoryName; + + this._watcher.Filter = "*.*"; + this._watcher.IncludeSubdirectories = true; + this._watcher.EnableRaisingEvents = true; + DefineEvents(); } - } - - Logger.Info (string.Format ("We have {0} application actions to do. Starting...", appActions.Count)); - - lock (this._appDomains) { - lock (this._loaderWorkers) { - foreach (var appAction in appActions) { - if (appAction.IsRemoved) { - UnloadApplication (appAction.ApplicationName); - Logger.Info (string.Format ("Application {0} removed from pool", appAction.ApplicationName)); - } else { - Logger.Info (string.Format ("Application {0} is avaliable", appAction.ApplicationName)); - LoadApplication (appAction.ApplicationName); + public void ForceReload() + { + lock (this._appDomains) + { + Logger.Info("Force reloading apps..."); + var keys = this._appDomains.Keys.ToList(); + foreach (var app in keys) + { + LoadApplication(app); // Load also unloads first + } } - } } - } - - this._timer.AutoReset = false; - this._timer.Enabled = false; - } - - /// - /// Create new application domain from folder with name - /// - /// Application Name - /// Application path - /// - private AppDomain CreateApplicationDomain (string applicationName, string applicationPath) { - Logger.Debug ( - string.Format ( - "Creating application domain for {0} from {1}", applicationName, applicationPath - ) - ); - - var applicationContext = new AssemblyLoadContext (applicationName, true); - var assembly = applicationContext.LoadFromAssemblyPath (applicationPath); - - var appDomain = AppDomain.CurrentDomain; - appDomain.Load (AssemblyName.GetAssemblyName (applicationPath)); - - return appDomain; - } - - /// - /// Validate if endpoint already exists inside Application - /// - /// Application name - /// Request path - /// HTTP Method - /// Returns true or false - public bool ContainsEndPoint (string appName, string path, string method) { - return this._loaderWorkers.ContainsKey (appName) && - this._loaderWorkers[appName].ContainsEndPoint (path, method); - } - - /// - /// Process endpoint request from loader Application - /// - /// Application name - /// Request path - /// HTTP Method - /// Rest Request - /// Returns a new Rest Response - public RestResponse Process (string appName, string path, string method, RestRequest request) { - var loaderWorker = this._loaderWorkers[appName]; - - if (loaderWorker != null) { - return loaderWorker.Process (path, method, request); - } - - return new RestResponse ("LoaderWorker not initialized", "text/plain", System.Net.HttpStatusCode.InternalServerError); - } - - /// - /// Create new LoaderWorker from Application Domain - /// - /// Application Domain - /// Returns a new instance of LoaderWorker - private LoaderWorker CreateLoaderWorker (AppDomain appDomain) { - var type = typeof (LoaderWorker); - return (LoaderWorker)appDomain.CreateInstanceAndUnwrap (type.Assembly.FullName, type.FullName); - } - - /// - /// Initialize the process and load all applications from application directory - /// - private void Init () { - if (!Directory.Exists (this._appsDir)) { - Directory.CreateDirectory (this._appsDir); - } - - var applications = Directory.GetDirectories (this._appsDir); - var applicationNames = applications - .Select (a => Path.GetFileName (a)) - .ToList (); - - foreach (var applicationName in applicationNames) { - LoadApplication (applicationName); - } - } - - /// - /// Event handler to trigger when a file is created - /// - /// Event source - /// Event arguments - private void onCreated (object source, FileSystemEventArgs e) { - if (!e.FullPath.Equals (this._appsDir)) { - ScheduleRefreshApplication (GetApplicationNameFromFolder (e.FullPath), false); - } - } - - /// - /// Event handler to trigger when a file is deleted - /// - /// Event source - /// Event arguments - private void onDeleted (object source, FileSystemEventArgs e) { - if (!e.FullPath.Equals (this._appsDir)) { - ScheduleRefreshApplication (GetApplicationNameFromFolder (e.FullPath), true); - } - } + /// + /// Define all event handlers + /// + private void DefineEvents() + { + this._timer.Elapsed += Timer_Elapsed; + this._watcher.Changed += new FileSystemEventHandler(onChanged); + this._watcher.Created += new FileSystemEventHandler(onCreated); + this._watcher.Deleted += new FileSystemEventHandler(onChanged); + this._watcher.Renamed += new RenamedEventHandler(onRenamed); + } - /// - /// Event handler to trigger when a file is changed - /// - /// Event source - /// Event arguments - private void onChanged (object source, FileSystemEventArgs e) { - if (!e.FullPath.Equals (this._appsDir)) { - ScheduleRefreshApplication (GetApplicationNameFromFolder (e.FullPath), !(File.Exists (e.FullPath) || Directory.Exists (e.FullPath))); - } - } + /// + /// Event triggered after start a timer to update assembly + /// + /// Event source + /// Event arguments + private void Timer_Elapsed(object sender, ElapsedEventArgs e) + { + Logger.Info("AppTimer Elapsed. Checking for application changes."); + + var appActions = new List(); + + lock (this._appActions) + { + var applicationNames = this._appActions.Keys.Select(x => x).ToArray(); + + foreach (var applicationName in applicationNames) + { + appActions.Add(this._appActions[applicationName]); + this._appActions.Remove(applicationName); + } + } - /// - /// Event handler to trigger when a file is renamed - /// - /// Event source - /// Event arguments - private void onRenamed (object source, RenamedEventArgs e) { - ScheduleRefreshApplication (GetApplicationNameFromFolder (e.OldFullPath), true); - ScheduleRefreshApplication (GetApplicationNameFromFolder (e.FullPath), false); - } + Logger.Info(string.Format("We have {0} application actions to do. Starting...", appActions.Count)); + + lock (this._appDomains) + { + lock (this._loaderWorkers) + { + foreach (var appAction in appActions) + { + if (appAction.IsRemoved) + { + UnloadApplication(appAction.ApplicationName); + Logger.Info(string.Format("Application {0} removed from pool", appAction.ApplicationName)); + } + else + { + Logger.Info(string.Format("Application {0} is avaliable", appAction.ApplicationName)); + LoadApplication(appAction.ApplicationName); + } + } + } + } - /// - /// Schedule application refresh when - /// - /// - /// - private void ScheduleRefreshApplication (string applicationName, bool isRemoved) { - Logger.Info ("Detected change for " + applicationName); - - lock (this._appActions) { - if (this._appActions.ContainsKey (applicationName)) { - this._appActions[applicationName].ApplicationName = applicationName; - this._appActions[applicationName].IsRemoved = isRemoved; - } else { - this._appActions.Add (applicationName, new AppAction (applicationName, isRemoved)); + this._timer.AutoReset = false; + this._timer.Enabled = false; } - } - - this._timer.Enabled = true; - this._timer.Start (); - } - /// - /// Get application name from folder - /// - /// Application folder - /// Application name - private string GetApplicationNameFromFolder (string folder) { - if (File.Exists (folder)) { - folder = Path.GetDirectoryName (folder); - } - - return folder - .Substring (folder.LastIndexOf ("apps" + Utils.DIRECTORY_SEPARATOR)) - .Split (Utils.DIRECTORY_SEPARATOR)[1]; - } + /// + /// Create new application domain from folder with name + /// + /// Application Name + /// Application path + /// + private AppDomain CreateApplicationDomain(string applicationName, string applicationPath) + { + Logger.Debug( + string.Format( + "Creating application domain for {0} from {1}", applicationName, applicationPath + ) + ); + + // Assembly.LoadFrom(applicationPath); + // var applicationContext = AssemblyLoadContext.Default; // new AssemblyLoadContext (applicationName, true); + //var assembly = applicationContext.LoadFromAssemblyPath (applicationPath); + + var appDomain = AppDomain.CurrentDomain; + appDomain.Load(AssemblyName.GetAssemblyName(applicationPath)); + + return appDomain; + } - /// - /// Unload application by name - /// - /// Application name - private void UnloadApplication (string applicationName) { - if (this._appDomains.ContainsKey (applicationName)) { - Logger.Info ("Unloading " + applicationName); - try { - this._loaderWorkers.Remove (applicationName); - AppDomain.Unload (this._appDomains[applicationName]); - this._appDomains.Remove (applicationName); - - var directory = new DirectoryInfo (Path.Combine (".", "deployed", applicationName)); - directory.Delete (true); - } catch (Exception ex) { - Logger.Error (string.Format ("Failed unloading application {0}:\n{1}", applicationName, ex.ToString ())); + /// + /// Validate if endpoint already exists inside Application + /// + /// Application name + /// Request path + /// HTTP Method + /// Returns true or false + public bool ContainsEndPoint(string appName, string path, string method) + { + return this._loaderWorkers.ContainsKey(appName) && + this._loaderWorkers[appName].ContainsEndPoint(path, method); } - } - } - /// - /// Deploy application from name - /// - /// - private void LoadApplication (string applicationName) { - UnloadApplication (applicationName); + /// + /// Process endpoint request from loader Application + /// + /// Application name + /// Request path + /// HTTP Method + /// Rest Request + /// Returns a new Rest Response + public RestResponse Process(string appName, string path, string method, RestRequest request) + { + var loaderWorker = this._loaderWorkers[appName]; + + if (loaderWorker != null) + { + return loaderWorker.Process(path, method, request); + } - Logger.Info ("Deploying application: " + applicationName); + return new RestResponse("LoaderWorker not initialized", "text/plain", System.Net.HttpStatusCode.InternalServerError); + } - var appPath = Path.Combine (Environment.CurrentDirectory, this._appsDir, applicationName); - var assemblies = Directory.GetFiles (appPath) - .Where (x => x.EndsWith (".dll")) - .ToList (); + /// + /// Create new LoaderWorker from Application Domain + /// + /// Application Domain + /// Returns a new instance of LoaderWorker + private LoaderWorker CreateLoaderWorker(AppDomain appDomain) + { + var type = typeof(LoaderWorker); + return (LoaderWorker)appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName); + } - LoaderWorker loaderWorker = new LoaderWorker (); + /// + /// Initialize the process and load all applications from application directory + /// + private void Init() + { + if (!Directory.Exists(this._appsDir)) + { + Directory.CreateDirectory(this._appsDir); + } - assemblies.ForEach (appAssembly => { - var applicationPath = Path.Combine (appPath, Path.GetFileName (appAssembly)); - var applicationDomain = CreateApplicationDomain (applicationName, applicationPath); - this._appDomains.Add (applicationName, applicationDomain); + var applications = Directory.GetDirectories(this._appsDir); + var applicationNames = applications + .Select(a => Path.GetFileName(a)) + .ToList(); - loaderWorker = CreateLoaderWorker (applicationDomain); + foreach (var applicationName in applicationNames) + { + LoadApplication(applicationName); + } + } - var targetPath = Path.Combine (this._deployedDir, applicationName); - var targetFile = Path.Combine (targetPath, Path.GetFileName (appAssembly)); + /// + /// Event handler to trigger when a file is created + /// + /// Event source + /// Event arguments + private void onCreated(object source, FileSystemEventArgs e) + { + if (!e.FullPath.Equals(this._appsDir)) + { + ScheduleRefreshApplication(GetApplicationNameFromFolder(e.FullPath), false); + } + } - var appDebugAssembly = appAssembly.Replace (".dll", Utils.DEBUG_SYMBOLS_EXTENSION); - var targetDebugFile = Path.Combine (targetPath, Path.GetFileName (appDebugAssembly)); + /// + /// Event handler to trigger when a file is deleted + /// + /// Event source + /// Event arguments + private void onDeleted(object source, FileSystemEventArgs e) + { + if (!e.FullPath.Equals(this._appsDir)) + { + ScheduleRefreshApplication(GetApplicationNameFromFolder(e.FullPath), true); + } + } - try { - if (!Directory.Exists (targetPath)) { - Directory.CreateDirectory (targetPath); - } + /// + /// Event handler to trigger when a file is changed + /// + /// Event source + /// Event arguments + private void onChanged(object source, FileSystemEventArgs e) + { + if (!e.FullPath.Equals(this._appsDir)) + { + ScheduleRefreshApplication(GetApplicationNameFromFolder(e.FullPath), !(File.Exists(e.FullPath) || Directory.Exists(e.FullPath))); + } + } - if (File.Exists (targetFile)) { - File.Delete (targetFile); - } + /// + /// Event handler to trigger when a file is renamed + /// + /// Event source + /// Event arguments + private void onRenamed(object source, RenamedEventArgs e) + { + ScheduleRefreshApplication(GetApplicationNameFromFolder(e.OldFullPath), true); + ScheduleRefreshApplication(GetApplicationNameFromFolder(e.FullPath), false); + } - if (File.Exists (targetDebugFile)) { - File.Delete (targetDebugFile); - } + /// + /// Schedule application refresh when + /// + /// + /// + private void ScheduleRefreshApplication(string applicationName, bool isRemoved) + { + Logger.Info("Detected change for " + applicationName); + + lock (this._appActions) + { + if (this._appActions.ContainsKey(applicationName)) + { + this._appActions[applicationName].ApplicationName = applicationName; + this._appActions[applicationName].IsRemoved = isRemoved; + } + else + { + this._appActions.Add(applicationName, new AppAction(applicationName, isRemoved)); + } + } - Logger.Info (string.Format ("Found {0}. Copying to {1}", appAssembly, targetFile)); - File.Copy (appAssembly, targetFile); + this._timer.Enabled = true; + this._timer.Start(); + } - if (File.Exists (appDebugAssembly)) { - Logger.Info (string.Format ("Found debug file for {0} at {1}", appAssembly, appDebugAssembly)); - File.Copy (appDebugAssembly, targetDebugFile); - } + /// + /// Get application name from folder + /// + /// Application folder + /// Application name + private string GetApplicationNameFromFolder(string folder) + { + if (File.Exists(folder)) + { + folder = Path.GetDirectoryName(folder); + } - loaderWorker.LoadAssembly (targetFile); - } catch (Exception ex) { - Logger.Error (string.Format ("Failed loading application assembly {0}:\n{1}", appAssembly, ex.ToString ())); + return folder + .Substring(folder.LastIndexOf("apps" + Utils.DIRECTORY_SEPARATOR)) + .Split(Utils.DIRECTORY_SEPARATOR)[1]; } - }); - this._loaderWorkers.Add (applicationName, loaderWorker); + /// + /// Unload application by name + /// + /// Application name + private void UnloadApplication(string applicationName) + { + if (this._appDomains.ContainsKey(applicationName)) + { + Logger.Info("Unloading " + applicationName); + try + { + this._loaderWorkers.Remove(applicationName); + AppDomain.Unload(this._appDomains[applicationName]); + this._appDomains.Remove(applicationName); + + var directory = new DirectoryInfo(Path.Combine(".", "deployed", applicationName)); + directory.Delete(true); + } + catch (Exception ex) + { + Logger.Error(string.Format("Failed unloading application {0}:\n{1}", applicationName, ex.ToString())); + } + } + } + private AppDomain createDomain(string name, string folder) + { + AppDomainSetup domaininfo = new AppDomainSetup(); + domaininfo.ApplicationBase = folder; + Evidence adevidence = AppDomain.CurrentDomain.Evidence; + return AppDomain.CreateDomain(name, adevidence, domaininfo); + } + /// + /// Deploy application from name + /// + /// + private void LoadApplication(string applicationName) + { + UnloadApplication(applicationName); + + Logger.Info("Deploying application: " + applicationName); + + var appPath = Path.Combine(Environment.CurrentDirectory, this._appsDir, applicationName); + var assemblies = Directory.GetFiles(appPath) + .Where(x => x.EndsWith(".dll")) + .ToList(); + var appDomain = createDomain(applicationName, Environment.CurrentDirectory); + this._appDomains.Add(applicationName, appDomain); + LoaderWorker loaderWorker = new LoaderWorker(); + + assemblies.ForEach(appAssembly => + { + string targetPath = Path.Combine(this._deployedDir, applicationName); + string targetFile = Path.Combine(targetPath, Path.GetFileName(appAssembly)); + string appDebugAssembly = appAssembly.Replace(".dll", Utils.DEBUG_SYMBOLS_EXTENSION); + string targetDebugFile = Path.Combine(targetPath, Path.GetFileName(appDebugAssembly)); + try + { + if (!Directory.Exists(targetPath)) + { + Directory.CreateDirectory(targetPath); + } + + if (File.Exists(targetFile)) + { + File.Delete(targetFile); + } + + if (File.Exists(targetDebugFile)) + { + File.Delete(targetDebugFile); + } + + Logger.Info(string.Format("Found {0}. Copying to {1}", appAssembly, targetFile)); + File.Copy(appAssembly, targetFile); + + if (File.Exists(appDebugAssembly)) + { + Logger.Info(string.Format("Found debug file for {0} at {1}", appAssembly, appDebugAssembly)); + File.Copy(appDebugAssembly, targetDebugFile); + } + + loaderWorker.LoadAssembly(targetFile); + } + catch (Exception ex) + { + Logger.Error(string.Format("Failed loading application assembly {0}:\n{1}", appAssembly, ex.ToString())); + } + }); + + this._loaderWorkers.Add(applicationName, loaderWorker); + } } - } } \ No newline at end of file