Skip to content

Commit 1d62fec

Browse files
committed
feat: Switch to HTTPListener
1 parent c059517 commit 1d62fec

File tree

2 files changed

+167
-68
lines changed

2 files changed

+167
-68
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
Copyright (C) 2025 Robyn (robyn@mamallama.dev)
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
*/
9+
10+
using System;
11+
using System.IO;
12+
using System.Net;
13+
using System.Text;
14+
using SharpWebserver.Caching;
15+
16+
namespace SharpWebserver;
17+
18+
public partial class ListenServer
19+
{
20+
private static void SendPageResponse(HttpListenerResponse response, string pageData, string mimeType, int statusCode = 200, string status = "OK")
21+
{
22+
var data = Encoding.UTF8.GetBytes(pageData);
23+
SendPageResponse(response, data, mimeType, statusCode, status);
24+
}
25+
26+
private static void SendPageResponse(HttpListenerResponse response, byte[] pageData, string mimeType, int statusCode = 200, string status = "OK")
27+
{
28+
response.ContentType = mimeType;
29+
response.ContentLength64 = pageData.LongLength;
30+
response.ContentEncoding = Encoding.UTF8;
31+
response.StatusCode = statusCode;
32+
response.StatusDescription = status;
33+
response.SendChunked = false;
34+
response.OutputStream.Write(pageData, 0, pageData.Length);
35+
response.Close();
36+
}
37+
38+
private static void ServePageToClient(HttpListenerContext context)
39+
{
40+
var request = context.Request;
41+
var response = context.Response;
42+
43+
44+
//Blocked clients
45+
if (!Utilities.SecurityPolicy.AllowClient(request.RemoteEndPoint))
46+
{
47+
Logger.LogWarning("Blocked client attempted to connect", [
48+
("Client", request.RemoteEndPoint)
49+
]);
50+
51+
SendPageResponse(response, "<html><body><h2>Forbidden</h2><p>Your client is forbidden from accessing this server</p></body></html>", "Text/HTML", 403, "Forbidden");
52+
return;
53+
}
54+
55+
#region Resolve Request
56+
57+
var uri = request.Url;
58+
59+
if (uri is null)
60+
{
61+
response.Close();
62+
return;
63+
}
64+
65+
var localPath = uri.AbsolutePath;
66+
string resolvedPath = Path.GetFullPath(Path.Combine(WebRoot, "." + localPath));
67+
68+
if (!resolvedPath.StartsWith(WebRoot))
69+
{
70+
Logger.LogWarning("Attempted to access below root", [
71+
("Request", localPath),
72+
("Resolved", resolvedPath),
73+
("Remote", request.RemoteEndPoint)
74+
]);
75+
76+
Utilities.SecurityPolicy.BanRemote(request.RemoteEndPoint);
77+
SendPageResponse(response, "Access to this resource is forbidden. Your client information has been logged", "Text/HTML", 403, "Forbidden");
78+
return;
79+
}
80+
81+
string fileName = Path.GetFileName(resolvedPath);
82+
if (string.IsNullOrEmpty(fileName))
83+
{
84+
resolvedPath = Path.Combine(resolvedPath, "Index.cs");
85+
}
86+
87+
//Logger.LogInfo("Finished path resolution", [
88+
//("Original", localPath),
89+
//("Resolved", resolvedPath)
90+
//]);
91+
92+
FileInfo file = new(resolvedPath);
93+
94+
if (SafeMode)
95+
{
96+
Logger.LogTrace("Serving safe mode document");
97+
SendPageResponse(response, $"<h2>File Served!</h2><p>Request: {file.FullName.Replace(BaseDir, string.Empty)}</p>", "Text/HTML");
98+
return;
99+
}
100+
101+
if (!file.Exists)
102+
{
103+
SendPageResponse(response, "<h2>404: File Not Found</h2><p>Resource not found</p>", "text/html", 404, "Not Found");
104+
return;
105+
}
106+
107+
var name = Path.GetFileName(resolvedPath).ToLowerInvariant();
108+
var chunks = name.Split('.');
109+
string contentType;
110+
111+
if (chunks.Length < 2)
112+
contentType = "text/plain";
113+
else
114+
contentType = Utilities.GetMimeType(chunks[1]);
115+
116+
if (name.EndsWith(".cs"))
117+
{
118+
var script = PageCache.FetchPage(file);
119+
120+
if (script is null)
121+
{
122+
SendPageResponse(response, "The requested resource failed to build", "text/html", 500, "Server Error");
123+
return;
124+
}
125+
126+
try
127+
{
128+
var data = script.CreatePage();
129+
SendPageResponse(response, data, contentType);
130+
return;
131+
}
132+
catch (Exception ex)
133+
{
134+
SendPageResponse(response, $"The requested resource encountered an exception while running {ex.ToString().Replace("<", "&gt;")}", "text/html", 500, "Server Error");
135+
return;
136+
}
137+
138+
}
139+
else
140+
{
141+
using var reader = file.OpenRead();
142+
byte[] fileContents = new byte[file.Length];
143+
reader.Read(fileContents, 0, (int)file.Length);
144+
SendPageResponse(response, fileContents, contentType);
145+
return;
146+
}
147+
148+
#endregion
149+
}
150+
}

src/SharpWebserver/Program.cs

Lines changed: 17 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@ it under the terms of the GNU General Public License as published by
1010
using System.Net;
1111
using System.Net.Sockets;
1212
using CSScriptLib;
13-
using SharpWebserver.Clients;
1413
using System;
15-
using System.Collections.Generic;
1614
using System.IO;
1715
using System.Threading.Tasks;
1816
using SharpWebserver.Config;
1917
using System.Runtime.InteropServices;
20-
using Tomlyn;
2118

2219
namespace SharpWebserver;
2320
partial class ListenServer
@@ -46,16 +43,6 @@ You should have received a copy of the GNU General Public License
4643
static async Task Main(string[] args)
4744
{
4845

49-
/*
50-
var GlobalToml = @"SafeMode = true
51-
PortNumber = 8080
52-
";
53-
54-
var BlockToml = @"BlockList = [
55-
'item', 'item2', 'item3'
56-
]
57-
";
58-
*/
5946
ProcessArguments(in args);
6047

6148
if (ExitNow)
@@ -64,9 +51,6 @@ static async Task Main(string[] args)
6451

6552
Console.WriteLine(LICENSE);
6653

67-
//Setup connected clients
68-
Dictionary<uint, ConnectedClient> clients = [];
69-
7054
var ver = typeof(ListenServer).Assembly.GetName().Version;
7155

7256
if (ver is null)
@@ -106,8 +90,10 @@ static async Task Main(string[] args)
10690
]);
10791

10892
//Define the local endpoint for the socket.
109-
IPAddress ipAddress = IPAddress.Any;
110-
TcpListener listener = new(ipAddress, GlobalConfig.PortNumber);
93+
HttpListener listener = new();
94+
95+
//Set valid prefixes
96+
listener.Prefixes.Add($"http://127.0.0.1:{GlobalConfig.PortNumber}/");
11197

11298
//Register the input handler
11399
var InputHandler = Task.Run(HandleInputAsync);
@@ -117,72 +103,35 @@ static async Task Main(string[] args)
117103
// Start listening for client requests.
118104
listener.Start();
119105
Logger.LogInfo("Server starting up", [
120-
("Bound IP", ipAddress),
121106
("Bound Port", GlobalConfig.PortNumber),
122107
("Status", "Waiting for connections")
123108
]);
124109

125-
void TryAcceptClientCallback(IAsyncResult ar)
110+
void ContextReceivedCallback(IAsyncResult ar)
126111
{
127-
var client = listener.EndAcceptTcpClient(ar);
112+
var context = listener.EndGetContext(ar);
128113

129-
if (client.Client.RemoteEndPoint is not EndPoint remote || remote.AddressFamily != AddressFamily.InterNetwork)
130-
{
131-
Logger.LogWarning("Refusing a client with bad or unsupported IP information");
132-
var response = Utilities.GenerateResponse(421u, "Misdirected Request", "<h1>Misdirected Request</h1><p>Unable to service your device's IP configuration</p>", "text/html", true);
133-
client.GetStream().Write(response, 0, response.Length);
134-
client.Close();
135-
return;
136-
}
114+
Logger.LogInfo("New Request", [
115+
("From", context.Request.RemoteEndPoint),
116+
("Document", context.Request.RawUrl),
117+
("Query Count", context.Request.QueryString.Count),
118+
("Has Post Data?", context.Request.HasEntityBody)
119+
]);
137120

138-
if (!Utilities.SecurityPolicy.AllowClient(remote))
139-
{
140-
Logger.LogWarning("Refusing a client due to security policy");
141-
var response = Utilities.GenerateResponse(403u, "Client Forbidden", "<h1>Client Forbidden</h1><p>Access denied, your client details have been logged</p>", "text/html");
142-
client.GetStream().Write(response, 0, response.Length);
143-
client.Close();
144-
return;
145-
}
146-
147-
var newClient = new ConnectedClient(client, remote);
148-
clients.TryAdd(newClient.ClientID, newClient);
121+
ServePageToClient(context);
122+
listener.BeginGetContext(ContextReceivedCallback, listener);
149123
}
150124

151-
listener.BeginAcceptTcpClient(TryAcceptClientCallback, listener);
125+
listener.BeginGetContext(ContextReceivedCallback, listener);
152126

153127
#region Main Loop
154128

155-
while (true)
129+
while (listener.IsListening)
156130
{
157-
if (ExitNow)
158-
{
159-
Logger.LogInfo("Exit requested by user");
160-
break;
161-
}
162-
163-
var ClientTasks = new List<Task>();
164-
165-
//Handle all requests from all clients
166-
foreach (var client in clients.Values)
167-
{
168-
ClientTasks.Add(Task.Run(client.CheckIn).ContinueWith(ClientTaskEnded, client));
169-
}
170-
171131
await Task.Delay(10);
172132
}
173-
#endregion
174133

175-
void ClientTaskEnded(Task<bool> task, object? state)
176-
{
177-
if (task.Result || state is not ConnectedClient client)
178-
return;
179-
180-
Logger.LogInfo("Reaping a client", [
181-
("ClientID", client.ClientID)
182-
]);
183-
184-
clients.Remove(client.ClientID);
185-
}
134+
#endregion
186135

187136
}
188137
catch (SocketException ex)

0 commit comments

Comments
 (0)