123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- using SimpleWebChat.ConHost;
- using System.Collections.Concurrent;
- using System.IO.Compression;
- using System.Net;
- using System.Text;
- internal static class HostProgram
- {
- private static readonly ConcurrentDictionary<string, LoadedModule> Modules = new();
- private static LoadedModule _defaultModule;
- private static bool _isLoading;
- private static bool _isRunning;
- private static DateTime _lastRequestAccepted;
- private static int _requestIdStore = 0;
- private static void Main(string[] args)
- {
- Console.WriteLine("Starting...");
- var tWorker = new Thread(Working);
- _isRunning = true;
- tWorker.Start();
- Task.Run(ReloadConfig);
- Console.WriteLine("Press ENTER to Stop.");
- Console.ReadLine();
- Console.WriteLine("Shutting down...");
- _isRunning = false;
- tWorker.Join();
- Console.WriteLine("Stopped.");
- Console.WriteLine();
- Console.Write("Press ENTER to Exit.");
- Console.ReadLine();
- }
- private static void ReloadConfig()
- {
- if (_isLoading)
- {
- Console.WriteLine("Still loading, SKIP");
- return;
- }
- _isLoading = true;
- try
- {
- ConfigFile.Reload();
- ReloadModulesInternal();
- }
- catch (Exception e)
- {
- Console.WriteLine($"Load error: {e}");
- }
- _isLoading = false;
- }
- private static void ReloadModulesInternal()
- {
- Modules.Clear();
- _defaultModule = null;
- if (ConfigFile.Instance.Modules?.Any() == true)
- {
- foreach (var modEnt in ConfigFile.Instance.Modules)
- {
- Console.WriteLine($"Loading module `{modEnt.Value.DisplayText}'...");
- var module = new LoadedModule
- {
- VirtualPath = modEnt.Key,
- DisplayText = modEnt.Value.DisplayText,
- DefaultDocument = modEnt.Value.DefaultDocument,
- EnableFallbackRoute = modEnt.Value.EnableFallbackRoute,
- HtmlBaseReplace = modEnt.Value.HtmlBaseReplace,
- Files = new Dictionary<string, byte[]>()
- };
- if (Directory.Exists(modEnt.Value.Path))
- {
- //load by fs
- var files = Directory.GetFiles(modEnt.Value.Path, "*", System.IO.SearchOption.AllDirectories);
- foreach (var item in files)
- {
- var k = item.Substring(modEnt.Value.Path.Length + 1).Replace("\\", "/").ToLower();
- module.Files[k] = File.ReadAllBytes(item);
- }
- }
- else if (File.Exists(modEnt.Value.Path))
- {
- //load by package
- using var arc = SharpCompress.Archives.ArchiveFactory.Open(modEnt.Value.Path);
- foreach (var ent in arc.Entries.Where(p => p.IsDirectory == false))
- {
- var buf = new byte[ent.Size];
- using var s = ent.OpenEntryStream();
- var r = s.Read(buf, 0, buf.Length);
- module.Files[ent.Key.ToLower()] = buf;
- }
- }
- else
- {
- Console.WriteLine("WARN: resource not found");
- continue;
- }
- if (modEnt.Value.IsDefault && _defaultModule == null) _defaultModule = module;
- Modules[modEnt.Key] = module;
- Console.WriteLine($"Module `{modEnt.Value.DisplayText}' loaded.");
- }
- }
- }
- private static void Working()
- {
- var listener = new HttpListener();
- listener.Prefixes.Add(ConfigFile.Instance.ListenPrefix);
- if (ConfigFile.Instance.AliasPrefix?.Any() == true)
- foreach (var prefix in ConfigFile.Instance.AliasPrefix)
- listener.Prefixes.Add(prefix);
- listener.Start();
- var upTime = DateTime.Now;
- Console.WriteLine($"HTTP Server started, listening on {string.Join("; ", listener.Prefixes)}");
- listener.BeginGetContext(ContextGet, listener);
- _lastRequestAccepted = DateTime.Now;
- while (_isRunning)
- {
- var timeSpan = DateTime.Now - _lastRequestAccepted;
- var up = DateTime.Now - upTime;
- Console.Title =
- "SimWebChat"
- + $" UP {up.Days:00}D {up.Hours:00}H {up.Minutes:00}M {up.Seconds:00}S {up.Milliseconds:000}"
- + $" / "
- + $" LA {timeSpan.Days:00}D {timeSpan.Hours:00}H {timeSpan.Minutes:00}M {timeSpan.Seconds:00}S {timeSpan.Milliseconds:000}"
- ;
- Thread.Sleep(1000);
- }
- listener.Close();
- Thread.Sleep(1000);
- }
- private static void ContextGet(IAsyncResult ar)
- {
- var listener = (HttpListener)ar.AsyncState;
- HttpListenerContext context;
- try
- {
- context = listener.EndGetContext(ar);
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- return;
- }
- if (_isRunning) listener.BeginGetContext(ContextGet, listener);
- ProcessRequest(context);
- }
- private static void ProcessRequest(HttpListenerContext context)
- {
- _lastRequestAccepted = DateTime.Now;
- var request = context.Request;
- var currentSessionId = Interlocked.Increment(ref _requestIdStore);
- Console.WriteLine($"Request #{currentSessionId:00000} from {request.RemoteEndPoint} {request.HttpMethod} {request.RawUrl}");
- try
- {
- var requestPath = request.Url.LocalPath.ToLower();
- var pathParts = (IReadOnlyList<string>)requestPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
- if (requestPath == "/" && _defaultModule != null)
- {
- if (_defaultModule.EnableFallbackRoute) context.Response.Redirect($"/modules/{_defaultModule.VirtualPath}/");
- else context.Response.Redirect($"/modules/{_defaultModule.VirtualPath}/{_defaultModule.DefaultDocument}");
- }
- else if (requestPath == "/connect" && request.IsWebSocketRequest)
- {
- SimpleWebChatServerModule.ProcessRequest(context, currentSessionId);
- }
- else if (requestPath == "/connect/voice" && request.IsWebSocketRequest)
- {
- SimpleVoiceChatServerModule.ProcessRequest(context, currentSessionId);
- }
- else if (requestPath == "/connect/voice/meeting" && request.IsWebSocketRequest)
- {
- SimpleVoiceMeetingServerModule.ProcessRequest(context, currentSessionId);
- }
- else if (requestPath.StartsWith("/modules/") && pathParts.Count > 1)
- {
- var moduleKey = pathParts[1];
- if (Modules.TryGetValue(moduleKey, out var module))
- {
- var entPath = string.Join("/", pathParts.Skip(2));
- void Output(byte[] bin)
- {
- if (entPath.ToLower().EndsWith(".js")) context.Response.ContentType = "application/javascript";
- else if (module.HtmlBaseReplace != null && entPath.ToLower().EndsWith(".html"))
- {
- //base replace
- var html = Encoding.UTF8.GetString(bin);
- var r = html.Replace(module.HtmlBaseReplace, $"<base href=\"/modules/{moduleKey}/\" />");
- bin = Encoding.UTF8.GetBytes(r);
- }
- context.Response.OutputStream.Write(bin, 0, bin.Length);
- }
- if (module.Files.TryGetValue(entPath, out var bin))
- {
- Output(bin);
- }
- else if (module.EnableFallbackRoute && module.Files.TryGetValue(module.DefaultDocument, out var defBin))
- {
- entPath = module.DefaultDocument;
- Output(defBin);
- }
- else context.Response.StatusCode = 404;
- }
- else context.Response.StatusCode = 404;
- }
- else if (requestPath == "/admin/" && false == request.QueryString.AllKeys.Contains("action"))
- {
- var sb = new StringBuilder();
- sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
- sb.Append($"<title> Admin - {ConfigFile.Instance.Title} </title>");
- sb.Append("<body bgColor=skyBlue>");
- sb.Append($"<h3>Admin</h3>");
- sb.Append("<div><a href=/>Back to home</a></div>");
- sb.Append($"<form method=GET>");
- sb.Append($"Password: <input type=password name=pass />");
- sb.Append($"<br/>");
- sb.Append($"Operation: ");
- sb.Append($"<input type=submit name=action value=Reload /> ");
- sb.Append($"</form>");
- context.WriteTextUtf8(sb.ToString());
- }
- else if (requestPath == "/admin/" && request.QueryString["action"] == "Reload" && request.QueryString["pass"] == ConfigFile.Instance.AdminPassword)
- {
- Task.Run(ReloadConfig);
- context.Response.Redirect("/");
- }
- else if (requestPath == "/admin/")
- {
- context.Response.Redirect("/");
- }
- else
- {
- context.Response.StatusCode = 404;
- }
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- try
- {
- context.Response.StatusCode = 500;
- }
- catch (Exception exception)
- {
- Console.WriteLine(exception);
- }
- }
- finally
- {
- try
- {
- if (request.IsWebSocketRequest)
- {
- }
- Console.WriteLine($"Request #{currentSessionId:00000} ends with status code: {context.Response.StatusCode}");
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
- try
- {
- context.Response.Close();
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
- }
- }
- public static void WriteTextUtf8(this HttpListenerContext context, string content, string contentType = "text/html")
- {
- var bytes = Encoding.UTF8.GetBytes(content);
- context.Response.ContentEncoding = Encoding.UTF8;
- context.Response.ContentType = contentType;
- if (true == context.Request.Headers["Accept-Encoding"]?.Contains("gzip"))
- {
- context.Response.AddHeader("Content-Encoding", "gzip");
- var memoryStream = new MemoryStream(bytes);
- var gZipStream = new GZipStream(context.Response.OutputStream, CompressionMode.Compress, false);
- memoryStream.CopyTo(gZipStream);
- gZipStream.Flush();
- }
- else
- {
- context.Response.OutputStream.Write(bytes);
- }
- }
- }
- internal class LoadedModule
- {
- public string VirtualPath { get; set; }
- public string DisplayText { get; set; }
- public string DefaultDocument { get; set; }
- public Dictionary<string, byte[]> Files { get; set; }
- public bool EnableFallbackRoute { get; set; }
- public string HtmlBaseReplace { get; set; }
- }
|