📜 ⬆️ ⬇️

.NET: Creating an extensible plug-in system to monitor and manage servers

Hello, I want to tell you about the project, which, in my opinion, is very interesting and, perhaps, useful to someone.

The task was to make the system extensible plugins, which will be written by different people. Plugins should collect information from servers (performance, versions of installed programs, database selections), or make some changes (restarting services, etc.). Communication with servers must be carried out using TCP protocol and a specific port. Results should be displayed on the HTML page. There are many servers and in different cities, the connection is very bad, the servers are not very productive, the software can be updated up to 10 times a day, in general, we need total control over their performance.

After the reasoning it was decided:




Development began with a description of the interface IPlugin:

')
namespace Interface { public interface IPlugin { string Name { get; } string Version { get; } string Author { get; } object Data(Dictionary<string, string> query); } } 


Where Data () function which service on request of the browser will call. And the query will contain the parameters.

The next step is to create the service:



Implementations of this algorithm

 class HostServer : IDisposable { private Thread mThread; private HttpListener listener; private static string sVersion { get; set; } private static string sName { get; set; } private static string sIPAdress { get; set; } private static List<IPlugin> sPlugins { get; set; } public HostServer(int port) { sVersion = "0.6"; sName = "ISHOST"; sIPAdress = "http://" + System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString() + ":" + port.ToString() + "/"; LoadPlugins(); listener = new HttpListener(); listener.Prefixes.Add(sIPAdress); } private static void LoadPlugins() { try { sPlugins = new List<IPlugin>(); string folder = System.AppDomain.CurrentDomain.BaseDirectory; string[] files = System.IO.Directory.GetFiles(folder, "*.dll"); foreach (string file in files) { IPlugin plugin = IsPlugin(file); if (plugin != null) sPlugins.Add(plugin); } } catch (Exception e) { ToLog(e.Message); } } private static IPlugin IsPlugin(byte[] file) { try { System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(file); foreach (Type type in assembly.GetTypes()) { Type iface = type.GetInterface("Interface.IPlugin"); if (iface != null) { IPlugin plugin = (IPlugin)Activator.CreateInstance(type); return plugin; } } } catch (Exception e) { ToLog(e.Message); } return null; } private static IPlugin IsPlugin(string file_url) { try { byte[] b = System.IO.File.ReadAllBytes(file_url); return IsPlugin(b); } catch (Exception e) { ToLog(e.Message); } return null; } public void Start() { mThread = new Thread(new ThreadStart(delegate() { try { listener.Start(); ToLog("Started on " + sIPAdress); while (true) { IAsyncResult result = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener); result.AsyncWaitHandle.WaitOne(); } } catch (Exception e) { if (e.GetType() != typeof(ObjectDisposedException)) ToLog(e.Message); } })); mThread.Start(); } public void Stop() { if (listener != null) listener.Close(); if (mThread != null) mThread.Join(10000); ToLog("Stoped"); } public static void ListenerCallback(IAsyncResult result) { try { HttpListener listener = (HttpListener)result.AsyncState; HttpListenerContext context = listener.EndGetContext(result); HttpListenerRequest request = context.Request; HttpListenerResponse response = context.Response; using (System.IO.Stream output = response.OutputStream) { if (request.HasEntityBody) { SaveNewPlugin(request.InputStream, response, output); } else { string ans = string.Empty; foreach (IPlugin plugin in sPlugins) { if (request.Url.LocalPath.Replace("/", "").ToLower().StartsWith(plugin.Name.ToLower())) { // Get Parameters from url Dictionary<string, string> query = new Dictionary<string, string>(); for (int i = 0; i < request.QueryString.Keys.Count; i++) { string pName = request.QueryString.Keys[i]; string pValue = Encoding.UTF8.GetString(request.ContentEncoding.GetBytes(request.QueryString[pName])); query.Add(pName, pValue); } //Get data from plugin try { object data = plugin.Data(query); if (data.GetType() == typeof(string)) { ans = (string)data; } else { //Convert data to JSON type ans = request.QueryString["callback"] + "(" + ConvertToJSON(data) + ")"; } } catch (Exception e) { ans = e.Message; } break; } } if (string.IsNullOrEmpty(ans)) { ans = GetMainPage(); } //Send data to user SendMessage(response, output, ans); output.Flush(); output.Close(); } } } catch (Exception e) { if (e.GetType() != typeof(ObjectDisposedException)) ToLog(e.Message); } } private static string GetMainPage() { StringBuilder sb = new StringBuilder(); sb.Append("<html>"); sb.Append("<head>"); sb.Append("<title>ISHOST " + sVersion + "</title>"); sb.Append("</head>"); sb.Append("<body>"); sb.Append("<table cellpadding=\"10px\">"); sb.Append("<thead>"); sb.Append("<tr>"); sb.Append("<td>"); sb.Append("<b>NAME</b>"); sb.Append("</td>"); sb.Append("<td>"); sb.Append("<b>VERSION</b>"); sb.Append("</td>"); sb.Append("<td>"); sb.Append("<b>AUTHOR</b>"); sb.Append("</td>"); sb.Append("</tr>"); sb.Append("</thead>"); foreach (IPlugin plugin in sPlugins) { sb.Append("<tr>"); sb.Append("<td>"); sb.Append(plugin.Name); sb.Append("</td>"); sb.Append("<td>"); sb.Append(plugin.Version); sb.Append("</td>"); sb.Append("<td>"); sb.Append(plugin.Author); sb.Append("</td>"); sb.Append("</tr>"); } sb.Append("</table>"); sb.Append("<div>"); sb.Append("<form action=\"" + sIPAdress + "\" enctype=\"multipart/form-data\" method=\"post\">"); sb.Append("<input type=\"file\" name=\"somename\" size=\"chars\">"); sb.Append("<input type=\"submit\" value=\"Send\">"); sb.Append("</form>"); sb.Append("</div>"); sb.Append("</body>"); sb.Append("</html>"); return sb.ToString(); } private static void SendMessage(HttpListenerResponse response, Stream output, string ans) { byte[] buffer = System.Text.Encoding.UTF8.GetBytes(ans); response.ContentLength64 = buffer.Length; output.Write(buffer, 0, buffer.Length); } private static string ConvertToJSON(object data) { string result = string.Empty; try { result = JsonConvert.SerializeObject(data, Formatting.Indented); } catch (Exception e) { ToLog(e.Message); } return result; } static void ToLog(string text) { try { string folder = System.AppDomain.CurrentDomain.BaseDirectory; using (StreamWriter sw = new StreamWriter(folder + @"\Log.txt", true)) { sw.WriteLine(text+ " : " + DateTime.Now); } } catch { } } public void Dispose() { Stop(); } } 


One note to update the plug-ins (DLL) without restarting the service, I read the bytes from the dll file, and did not load the file into memory. Then read bytes checked for interface implementation. This method seemed to me better than the time-consuming unloading from the memory of an open dll.
Converting objects into JSON markup was done using the Newtonsoft library.

Service code for listening to the port.

 class Program: ServiceBase { HostServer server; public Program() { this.ServiceName = "ISHOST"; server = new HostServer(28555); } protected override void OnStart(string[] args) { server.Start(); base.OnStart(args); } protected override void OnStop() { server.Stop(); base.OnStop(); } static void Main(string[] args) { System.ServiceProcess.ServiceBase.Run(new Program()); } } 


Service installer

 [RunInstaller(true)] public class ISHOSTInstaller : Installer { public ISHOSTInstaller() { var processInstaller = new ServiceProcessInstaller(); var serviceInstaller = new ServiceInstaller(); processInstaller.Account = ServiceAccount.LocalSystem; serviceInstaller.DisplayName = "ISHOST"; serviceInstaller.StartType = ServiceStartMode.Automatic; serviceInstaller.ServiceName = "ISHOST"; this.Installers.Add(processInstaller); this.Installers.Add(serviceInstaller); } } 


After installing the service on the server using the InstallUtil.exe utility (InstallUtil ISHOST.exe), go to the server address 10.14.3.160 : 28555. We will open a page of this type:
image

Create a plugin

 class TestPlugin : IPlugin { public string Author { get { return "Yura"; } } public string Name { get { return "TestPlugin"; } } public string Version { get { return "0.1"; } } public object Data(Dictionary<string, string> query) { return "<h1>HELLO</h1>"; } } 

After compiling, go back to the browser, and select the TestPlugin.dll plugin:
image

After clicking the “Send” button.

image

Call TestPlugin plugin

image

In the next article I will write how to collect information using JavaScript and jQuery, as well as lay out the source code.
I look forward to your comments.

Source: https://habr.com/ru/post/131863/


All Articles