using System.IO.Compression; using System.Net; using System.Net.Sockets; using System.Text; namespace myownhttp { internal class Program { public enum HTTPMeth { undefined = -1, GET = 0, POST = 1 // add more in future } public class HTTPReq { public bool valid = false; public string Path = ""; public string HTTPVersion = ""; public string Content = ""; public HTTPMeth Method = HTTPMeth.undefined; public Dictionary Headers = new Dictionary(); public HTTPReq(string request) { try { var split = request.ReplaceLineEndings().Split(Environment.NewLine); var splitfirst = split[0].Split(" "); if (splitfirst[0] == "GET") Method = HTTPMeth.GET; else if (splitfirst[0] == "POST") Method = HTTPMeth.POST; Path = splitfirst[1]; // check if the path HTTPVersion = splitfirst[2]; // headers for (int i = 1; i < split.Length; i++) { var e = split[i]; if(e == "") { if(i+1 < split.Length) { var arraycontent = split.Skip(i); foreach(var item in arraycontent) { Content += item + "\r\n"; } } break; } var esplit = e.Split(": "); Headers.Add(esplit[0], esplit[1]); } valid = true; } catch (Exception e) { valid = false; } } public override string ToString() { return $"HTTP REQUEST -> {(valid ? "Valid" : "Invalid")}, {(Method switch { HTTPMeth.undefined => "Unset/Undefined", HTTPMeth.GET => "GET", HTTPMeth.POST => "POST", _ => "MError"})} {Path} {HTTPVersion}"; } } public class HTTPRes { byte[] contentBytes = []; public byte[] GetBytes() { return contentBytes; } public HTTPRes SetBytes(byte[] bytes) { contentBytes = bytes; return this; } public HTTPRes SetBytes(string str) { contentBytes = Encoding.UTF8.GetBytes(str); return this; } public string ContentType = GetMimeType("txt"); string Status = GetStatusFromNum(200); public string GetStatus() { return Status; } public HTTPRes SetStatus(int num) { SetStatus(GetStatusFromNum(num)); return this; } public HTTPRes SetStatus(string status) { Status = status; return this; } public byte[] Build() { return Encoding.UTF8.GetBytes( $"HTTP/1.1 {Status}\r\n" + $"Content-Type: {ContentType}{(ContentType.Contains("text") ? "; charset=utf-8" : "")}\n" + $"Content-Length: {contentBytes.Length}" + $"\n" + $"\n" ).Concat(contentBytes).ToArray(); } } public abstract class HTTPHandler { public HTTPHandler() { } public virtual void Handle(HTTPReq request, HTTPRes response, Action next) { // this is just a base, do nothing next(); } } public class HTTPGetHandler : HTTPHandler { string Path = ""; Action Action = (HTTPReq req, HTTPRes res, Action next) => { }; public HTTPGetHandler(string path, Action action) { Path = path; Action = action; } public override void Handle(HTTPReq request, HTTPRes response, Action next) { if (request.Path == Path) Action(request, response, next); else next(); } } public class HTTPServeFileHandler : HTTPHandler { HTTPGetHandler replacement; public HTTPServeFileHandler(string path, string filepath, string MimeType) { replacement = new(path, (req, res, next) => { if (File.Exists(filepath)) { res.SetBytes(File.ReadAllBytes(filepath)).SetStatus(200).ContentType = MimeType; } else next(); }); } public override void Handle(HTTPReq request, HTTPRes response, Action next) { replacement.Handle(request, response, next); } } public class HTTPStaticFilesHandler : HTTPHandler { string Files = ""; public HTTPStaticFilesHandler(string pathWithStatic) { Files = pathWithStatic; } public override void Handle(HTTPReq request, HTTPRes response, Action next) { var pat = request.Path.Replace("..", ""); if (pat.EndsWith("/")) pat += "index.html"; if (File.Exists(Files + Path.DirectorySeparatorChar + pat)) { var ex = Path.GetExtension(Files + Path.DirectorySeparatorChar + pat); if (ex == string.Empty) ex = ".txt"; ex = ex.Substring(1); response.SetBytes(File.ReadAllBytes(Files + Path.DirectorySeparatorChar + pat)).SetStatus(200).ContentType = GetMimeType(ex); } else next(); } } public class HTTPConditionHandler : HTTPHandler { HTTPHandler Handler = null; Func Condition = (HTTPReq request, HTTPRes response) => { return false; }; public HTTPConditionHandler(Func cond, HTTPHandler handler) { Handler = handler; } public override void Handle(HTTPReq request, HTTPRes response, Action next) { if(Handler == null || !Condition(request,response)) { next(); return; } Handler.Handle(request, response, next); } } public class HTTPServer { TcpListener listener; public HTTPServer(int port) { listener = new(IPAddress.Any, 8003); } public List Handlers = new(); public void Listen() { listener.Start(); while (true) { try { var a = listener.AcceptTcpClient(); var s = a.GetStream(); byte[] bytes = new byte[2048]; string data = null; int i; // Loop to receive all the data sent by the client. while ((i = s.Read(bytes, 0, bytes.Length)) != 0) { // Translate data bytes to a ASCII string. data = Encoding.ASCII.GetString(bytes, 0, i); var req = new HTTPReq(data); Console.WriteLine(req.ToString()); if (req.valid) { HTTPRes res = new HTTPRes(); int nextAct = 0; Action next = () => { }; next = () => { if(nextAct >= Handlers.Count) { res.SetBytes("404 Not found.").SetStatus(404).ContentType = GetMimeType("txt"); } else { Handlers[nextAct].Handle(req, res, () => { nextAct++; next(); }); } }; next(); s.Write(res.Build()); } else { // Send back a response. var nf = "uuh"; var nfb = Encoding.UTF8.GetBytes(nf); s.Write( Encoding.UTF8.GetBytes( $"HTTP/1.1 404 Not Found\n" + $"Content-Type: text/plain; charset=utf-8\n" + $"Content-Length: {nfb.Length}" + $"\n" + $"\n" + $"{nf}" ) ); } } } catch (Exception ex) { Console.WriteLine(ex); } } } } static void Main(string[] args) { Console.WriteLine("hello"); HTTPServer http = new(8003); http.Handlers.Add(new HTTPStaticFilesHandler("htdocs")); /*http.Handlers.Add(new HTTPGetHandler("/", (req, res, next) => { res.SetStatus(200).SetBytes("Hello! This is a test"); })); http.Handlers.Add(new HTTPGetHandler("/2", (req, res, next) => { res.SetStatus(200).SetBytes("Hello! This is a second test!"); })); http.Handlers.Add(new HTTPServeFileHandler("/favicon.ico", "favicon.ico",GetMimeType("ico")));*/ http.Listen(); } private static string GetStatusFromNum(int v) { return v switch { 200 => "200 OK", 400 => "400 Bad Request", 401 => "401 Unauthorized", 403 => "403 Forbidden", 404 => "404 Not Found", _ => "501 Not Implemented" }; } private static string GetMimeType(string v) { /* image/apng: Animated Portable Network Graphics (APNG) image/avif : AV1 Image File Format (AVIF) image/gif: Graphics Interchange Format (GIF) image/jpeg: Joint Photographic Expert Group image (JPEG) image/png: Portable Network Graphics (PNG) image/svg+xml: Scalable Vector Graphics (SVG) image/webp: Web Picture format (WEBP) */ return v switch { // text "txt" => "text/plain", "css" => "text/css", "js" => "text/javascript", "html" => "text/html", "json" => "application/json", // image "webp" => "image/webp", "png" => "image/png", "jpg" or "jpeg" => "image/jpeg", "svg" => "image/svg+xml", "gif" => "image/gif", "ico" => "image/x-icon", // just download it man _ => "application/octet-stream" }; } } }