diff --git a/WebServer.Test/Program.cs b/WebServer.Test/Program.cs new file mode 100644 index 0000000..1c6dc09 --- /dev/null +++ b/WebServer.Test/Program.cs @@ -0,0 +1,9 @@ +using System; + +namespace WebServer.Test { + internal class Program { + internal static void Main(string[] args) { + Console.WriteLine("Hello, World!"); + } + } +} \ No newline at end of file diff --git a/WebServer.Test/Views/HelloWorld.txt b/WebServer.Test/Views/HelloWorld.txt new file mode 100644 index 0000000..c57eff5 --- /dev/null +++ b/WebServer.Test/Views/HelloWorld.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/WebServer.Test/Views/Test.cshtml b/WebServer.Test/Views/Test.cshtml new file mode 100644 index 0000000..efbf544 --- /dev/null +++ b/WebServer.Test/Views/Test.cshtml @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/WebServer.Test/WebServer.Test.csproj b/WebServer.Test/WebServer.Test.csproj new file mode 100644 index 0000000..f009d3c --- /dev/null +++ b/WebServer.Test/WebServer.Test.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + disable + enable + true + true + + + + + + + + + + PreserveNewest + + + + + + + + + + + + diff --git a/WebServer.sln b/WebServer.sln index a00c5c2..7476302 100644 --- a/WebServer.sln +++ b/WebServer.sln @@ -3,7 +3,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35122.118 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebServer", "WebServer\WebServer.csproj", "{32E22CD1-CBE3-45E5-BADF-CE83A3096624}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer.Test", "WebServer.Test\WebServer.Test.csproj", "{36053856-E83C-4040-92CD-C825B6D11BD7}" +EndProject Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {32E22CD1-CBE3-45E5-BADF-CE83A3096624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32E22CD1-CBE3-45E5-BADF-CE83A3096624}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32E22CD1-CBE3-45E5-BADF-CE83A3096624}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32E22CD1-CBE3-45E5-BADF-CE83A3096624}.Release|Any CPU.Build.0 = Release|Any CPU + {36053856-E83C-4040-92CD-C825B6D11BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36053856-E83C-4040-92CD-C825B6D11BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36053856-E83C-4040-92CD-C825B6D11BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36053856-E83C-4040-92CD-C825B6D11BD7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection diff --git a/WebServer/HttpConfiguration.cs b/WebServer/HttpConfiguration.cs new file mode 100644 index 0000000..bcf7d16 --- /dev/null +++ b/WebServer/HttpConfiguration.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace WebServer { + public class HttpConfiguration { + public bool AutoStart = false; + public ushort Port = 80; + public string DefaultDomain = "localhost"; // The domain that the server will fallback on if the + public bool ShowExceptionOnErrorPages = true; // On InternalServerError(500), should it show the exception? + public ushort MaxConcurrentRequests = 100; + } +} diff --git a/WebServer/HttpServer.cs b/WebServer/HttpServer.cs new file mode 100644 index 0000000..c5b9cb2 --- /dev/null +++ b/WebServer/HttpServer.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace WebServer { + public class HttpServer { + public readonly HttpConfiguration Config; + public readonly DirectoryInfo ViewsDirectory; + public ushort ActivePort { get; protected set; } + public HttpListener HttpListener { get; protected set; } + public IHttpLogger Logger; + + Dictionary>>> HttpCallbacks = new Dictionary>>>(); + /* Instead of three directories (from previous render engine) + * - Public/PublicTemplates (was public static stuff, also allowed views) + * - Static (was used for builds) + * - Views/PrivateTemplates (was used for private views) + * There will be only two: + * - Static - For builds, there should also be a build process for this + * - Views - Site source, including public/private views (views are able to be processed) + */ + + public HttpServer(HttpConfiguration config, IHttpLogger logger = null) { + Config = config; + Logger = logger; + ViewsDirectory = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Views")); + if (!ViewsDirectory.Exists) + ViewsDirectory.Create(); + if (!string.IsNullOrEmpty(config.DefaultDomain)) + ViewsDirectory.CreateSubdirectory(config.DefaultDomain); + if (config.AutoStart) + _ = StartAsync(); + } + + public async Task StartAsync() { + if (HttpListener?.IsListening == true) + await StopAsync(); + HttpListener = new HttpListener() { + IgnoreWriteExceptions = true // Used to crash the server, don't think it is needed anymore + }; + HttpListener.Prefixes.Add($"http://+:{Config.Port}/"); + + HttpListener.Start(); + ActivePort = Config.Port; + Logger?.Log($"HTTP Service started on port '{ActivePort}'"); + + while (HttpListener.IsListening) + await ListenAsync((ListenToken = new CancellationTokenSource()).Token); + } + + CancellationTokenSource ListenToken; + public int TasksCount => Tasks.Count; + HashSet Tasks = new HashSet(); + async Task ListenAsync(CancellationToken token) { + Tasks = new HashSet(64); + for (int i = 0; i < Tasks.Count; i++) // Create 64 tasks + Tasks.Add(HttpListener.GetContextAsync()); + Logger.Log($"Listening with {Tasks.Count} worker(s)"); + while (!token.IsCancellationRequested) { + Task t = await Task.WhenAny(Tasks); + Tasks.Remove(t); + + if (t is Task context) { + if (Tasks.Count < Config.MaxConcurrentRequests) + Tasks.Add(HttpListener.GetContextAsync()); + Tasks.Add(ProcessRequestAsync(context.Result, token)); + } + else Logger.Log($"Got an unexpected task of type '{t.GetType().FullName}'"); + } + } + + public async Task StopAsync() { + HttpListener.Stop(); + ListenToken.Cancel(); + } + + public async Task ProcessRequestAsync(HttpListenerContext context, CancellationToken token) { } + } +} diff --git a/WebServer/IHttpLogger.cs b/WebServer/IHttpLogger.cs new file mode 100644 index 0000000..e1f278a --- /dev/null +++ b/WebServer/IHttpLogger.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Text; + +namespace WebServer { + public interface IHttpLogger { + void Log(string message); + void LogRequest(HttpListenerContext context, List methodsUsed); + void LogError(HttpListenerContext context, List methodsUsed, Exception exception); + } +}