using System; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Threading; using DHT.Server.Database; using DHT.Server.Logging; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; namespace DHT.Server.Service { public static class ServerLauncher { private static IWebHost? Server { get; set; } = null; public static bool IsRunning { get; private set; } public static event EventHandler? ServerStatusChanged; public static event EventHandler? ServerManagementExceptionCaught; private static Thread? ManagementThread { get; set; } = null; private static readonly Mutex ManagementThreadLock = new(); private static readonly BlockingCollection Messages = new(new ConcurrentQueue()); private static void EnqueueMessage(IMessage message) { ManagementThreadLock.WaitOne(); try { if (ManagementThread == null) { ManagementThread = new Thread(RunManagementThread) { IsBackground = true }; ManagementThread.Start(); } Messages.Add(message); } finally { ManagementThreadLock.ReleaseMutex(); } } [SuppressMessage("ReSharper", "FunctionNeverReturns")] private static void RunManagementThread() { foreach (IMessage message in Messages.GetConsumingEnumerable()) { try { switch (message) { case IMessage.StartServer start: StopServerFromManagementThread(); StartServerFromManagementThread(start.Port, start.Token, start.Db); break; case IMessage.StopServer: StopServerFromManagementThread(); break; } } catch (Exception e) { ServerManagementExceptionCaught?.Invoke(null, e); } } } private static void StartServerFromManagementThread(int port, string token, IDatabaseFile db) { Log.Info("Starting server on port " + port + "..."); void AddServices(IServiceCollection services) { services.AddSingleton(typeof(IDatabaseFile), db); services.AddSingleton(typeof(ServerParameters), new ServerParameters { Token = token }); } void SetKestrelOptions(KestrelServerOptions options) { options.Limits.MaxRequestBodySize = null; options.Limits.MinResponseDataRate = null; options.ListenLocalhost(port, listenOptions => listenOptions.Protocols = HttpProtocols.Http1); } Server = WebHost.CreateDefaultBuilder() .ConfigureServices(AddServices) .UseKestrel(SetKestrelOptions) .UseStartup() .Build(); Server.Start(); Log.Info("Server started"); IsRunning = true; ServerStatusChanged?.Invoke(null, EventArgs.Empty); } private static void StopServerFromManagementThread() { if (Server != null) { Log.Info("Stopping server..."); Server.StopAsync().Wait(); Log.Info("Server stopped"); IsRunning = false; ServerStatusChanged?.Invoke(null, EventArgs.Empty); Server = null; } } public static void Relaunch(int port, string token, IDatabaseFile db) { EnqueueMessage(new IMessage.StartServer(port, token, db)); } public static void Stop() { EnqueueMessage(new IMessage.StopServer()); } private interface IMessage { public sealed class StartServer : IMessage { public int Port { get; } public string Token { get; } public IDatabaseFile Db { get; } public StartServer(int port, string token, IDatabaseFile db) { this.Port = port; this.Token = token; this.Db = db; } } public sealed class StopServer : IMessage {} } } }