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
This commit is contained in:
9
WebServer.Test/Program.cs
Normal file
9
WebServer.Test/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace WebServer.Test {
|
||||
internal class Program {
|
||||
internal static void Main(string[] args) {
|
||||
Console.WriteLine("Hello, World!");
|
||||
}
|
||||
}
|
||||
}
|
||||
1
WebServer.Test/Views/HelloWorld.txt
Normal file
1
WebServer.Test/Views/HelloWorld.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello World!
|
||||
1
WebServer.Test/Views/Test.cshtml
Normal file
1
WebServer.Test/Views/Test.cshtml
Normal file
@@ -0,0 +1 @@
|
||||
Hello World!
|
||||
31
WebServer.Test/WebServer.Test.csproj
Normal file
31
WebServer.Test/WebServer.Test.csproj
Normal file
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Not entirely sure what this does -->
|
||||
<None Remove="Views\**" />
|
||||
<!-- Remove all Razor pages from being compiled in project -->
|
||||
<Content Remove="Views/**.cshtml" />
|
||||
<!-- Copy everything from views into output directory-->
|
||||
<None Include="Views/**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<!-- Do not compile contents -->
|
||||
<Compile Remove="Views\**" />
|
||||
<!-- But, inlude in Editor -->
|
||||
<Content Include="Views\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebServer\SimpleWebServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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
|
||||
|
||||
13
WebServer/HttpConfiguration.cs
Normal file
13
WebServer/HttpConfiguration.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
84
WebServer/HttpServer.cs
Normal file
84
WebServer/HttpServer.cs
Normal file
@@ -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<string, Dictionary<Regex, Func<HttpListenerRequest, Task<HttpResponse?>>>> HttpCallbacks = new Dictionary<string, Dictionary<Regex, Func<HttpListenerRequest, Task<HttpResponse?>>>>();
|
||||
/* 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<Task> Tasks = new HashSet<Task>();
|
||||
async Task ListenAsync(CancellationToken token) {
|
||||
Tasks = new HashSet<Task>(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<HttpListenerContext> 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) { }
|
||||
}
|
||||
}
|
||||
13
WebServer/IHttpLogger.cs
Normal file
13
WebServer/IHttpLogger.cs
Normal file
@@ -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<MethodBase> methodsUsed);
|
||||
void LogError(HttpListenerContext context, List<MethodBase> methodsUsed, Exception exception);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user