From 8980b384e79169696ab3d6d99dffbe3f3bd8ccbf Mon Sep 17 00:00:00 2001
From: Kaveman <55042475+Kaveinator@users.noreply.github.com>
Date: Mon, 12 Aug 2024 02:55:12 -0700
Subject: [PATCH] Create HttpServer
+ Added IHttpLogger to as a log provider to log Http events or errors
+ Added HttpConfiguration to more easily configure the server
+ WebServer.Test project that will be used to test the server
---
WebServer.Test/Program.cs | 9 +++
WebServer.Test/Views/HelloWorld.txt | 1 +
WebServer.Test/Views/Test.cshtml | 1 +
WebServer.Test/WebServer.Test.csproj | 31 ++++++++++
WebServer.sln | 18 ++++++
WebServer/HttpConfiguration.cs | 13 +++++
WebServer/HttpServer.cs | 84 ++++++++++++++++++++++++++++
WebServer/IHttpLogger.cs | 13 +++++
8 files changed, 170 insertions(+)
create mode 100644 WebServer.Test/Program.cs
create mode 100644 WebServer.Test/Views/HelloWorld.txt
create mode 100644 WebServer.Test/Views/Test.cshtml
create mode 100644 WebServer.Test/WebServer.Test.csproj
create mode 100644 WebServer/HttpConfiguration.cs
create mode 100644 WebServer/HttpServer.cs
create mode 100644 WebServer/IHttpLogger.cs
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);
+ }
+}