using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Web; // ============================================================ // A Simple Web Server - Pure C# Console Application // No ASP.NET, No Kestrel, No Framework // Just a TcpListener, raw HTTP parsing, and your code. // ============================================================ class Program { // --- In-memory data store (replace with real database) --- static List People = new List { new Person { Id = 1, Name = "Adam", Tel = "1111122222" }, new Person { Id = 2, Name = "May", Tel = "3333344444" }, new Person { Id = 3, Name = "Smith", Tel = "5555566666" } }; static int NextId = 4; static void Main(string[] args) { int port = 8007; TcpListener listener = new TcpListener(IPAddress.Any, port); listener.Start(); Console.WriteLine($"Server started. Listening on port {port}..."); Console.WriteLine($"Open browser: http://localhost:{port}/"); while (true) { TcpClient client = listener.AcceptTcpClient(); try { HandleClient(client); } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); } finally { client.Close(); } } } // ========================================================== // Layer 1: Receive raw bytes, parse HTTP, route request // ========================================================== static void HandleClient(TcpClient client) { NetworkStream stream = client.GetStream(); stream.ReadTimeout = 5000; // Wait briefly for data to arrive if (!stream.DataAvailable) { // Give the client a moment to send data System.Threading.Thread.Sleep(500); if (!stream.DataAvailable) return; // nothing came, abandon } // --- Step 1: Read the header section byte by byte until \r\n\r\n --- StringBuilder headerBuilder = new StringBuilder(); int prev3 = 0, prev2 = 0, prev1 = 0, current = 0; bool headerComplete = false; while (true) { int b = stream.ReadByte(); if (b == -1) return; // connection closed headerBuilder.Append((char)b); prev3 = prev2; prev2 = prev1; prev1 = current; current = b; // Detect \r\n\r\n (end of headers) if (prev3 == '\r' && prev2 == '\n' && prev1 == '\r' && current == '\n') { headerComplete = true; break; } } if (!headerComplete) return; string headerSection = headerBuilder.ToString(); // --- Step 2: Parse Content-Length from headers --- int contentLength = 0; string[] headerLines = headerSection.Split(new[] { "\r\n" }, StringSplitOptions.None); foreach (string line in headerLines) { if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase)) { string val = line.Substring("Content-Length:".Length).Trim(); int.TryParse(val, out contentLength); break; } } // --- Step 3: Read exactly Content-Length bytes for the body --- string body = ""; if (contentLength > 0) { byte[] bodyBuffer = new byte[contentLength]; int totalRead = 0; while (totalRead < contentLength) { int read = stream.Read(bodyBuffer, totalRead, contentLength - totalRead); if (read == 0) break; // connection closed totalRead += read; } body = Encoding.UTF8.GetString(bodyBuffer, 0, totalRead); } // --- Step 4: Parse into our HttpRequest model --- HttpRequest request = ParseHttpRequest(headerSection, body); Console.WriteLine($"{request.Method} {request.Path}"); // --- Step 5: Route to handler --- // After parsing the request, before RouteRequest: if (request.Path == "/favicon.ico") { // 1x1 transparent PNG (68 bytes) byte[] pixel = Convert.FromBase64String( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIABQABNjN9GQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAA0lEQVQI12P4z8BQDwAEgAF/QualIQAAAABJRU5ErkJggg==" ); string header = "HTTP/1.1 200 OK\r\n" + "Content-Type: image/png\r\n" + $"Content-Length: {pixel.Length}\r\n" + "Connection: close\r\n" + "\r\n"; byte[] headerBytes = Encoding.UTF8.GetBytes(header); stream.Write(headerBytes, 0, headerBytes.Length); stream.Write(pixel, 0, pixel.Length); stream.Flush(); return; // skip the normal HTML response path } string responseHtml = RouteRequest(request); // --- Step 6: Write HTTP response back as raw bytes --- string httpResponse = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + $"Content-Length: {Encoding.UTF8.GetByteCount(responseHtml)}\r\n" + "Connection: close\r\n" + "\r\n" + responseHtml; byte[] responseBytes = Encoding.UTF8.GetBytes(httpResponse); stream.Write(responseBytes, 0, responseBytes.Length); stream.Flush(); } // ========================================================== // HTTP Parser: Convert raw bytes into a structured object // This is what Kestrel does internally (simplified) // ========================================================== static HttpRequest ParseHttpRequest(string headerSection, string body) { HttpRequest request = new HttpRequest(); // Parse request line: GET /path HTTP/1.1 string[] lines = headerSection.Split(new[] { "\r\n" }, StringSplitOptions.None); string[] requestLine = lines[0].Split(' '); request.Method = requestLine[0].ToUpper(); request.Path = requestLine.Length > 1 ? requestLine[1] : "/"; // Parse headers for (int i = 1; i < lines.Length; i++) { int colon = lines[i].IndexOf(':'); if (colon > 0) { string key = lines[i].Substring(0, colon).Trim(); string value = lines[i].Substring(colon + 1).Trim(); request.Headers[key] = value; } } // Parse query string: /path?key=value&key2=value2 int qIndex = request.Path.IndexOf('?'); if (qIndex >= 0) { string queryString = request.Path.Substring(qIndex + 1); request.Path = request.Path.Substring(0, qIndex); ParseFormData(queryString, request.Query); } // Parse form body (application/x-www-form-urlencoded) if (request.Method == "POST" && body.Length > 0) { ParseFormData(body, request.Form); } // Normalize path request.Path = request.Path.ToLower().Trim().TrimEnd('/'); if (request.Path == "") request.Path = "/"; return request; } static void ParseFormData(string data, Dictionary target) { string[] pairs = data.Split('&'); foreach (string pair in pairs) { string[] kv = pair.Split(new[] { '=' }, 2); string key = HttpUtility.UrlDecode(kv[0]); string value = kv.Length > 1 ? HttpUtility.UrlDecode(kv[1]) : ""; target[key] = value; } } // ========================================================== // Router: Map path to handler // ========================================================== static string RouteRequest(HttpRequest request) { string path = request.Path; switch (path) { case "/": case "/home": return HandleHome(request); case "/about": return HandleAbout(request); default: return HandleNotFound(request); } } // ========================================================== // Layer 2: Application logic / Response handlers // ========================================================== static string HandleHome(HttpRequest request) { string action = request.GetForm("action"); switch (action) { case "edit": return ShowEdit(request); case "save": return SaveData(request); case "delete": return DeleteData(request); default: return ListData(request); } } // --- List all persons --- static string ListData(HttpRequest request) { StringBuilder rows = new StringBuilder(); foreach (var p in People) { rows.Append($@" {p.Id} {HtmlEncode(p.Name)} {HtmlEncode(p.Tel)}
"); } return WrapHtml("Home - Person List", $@"

Person List

{rows}
ID Name Tel Action

"); } // --- Show edit form --- static string ShowEdit(HttpRequest request) { int personId = request.GetFormInt("personid"); string name = ""; string tel = ""; if (personId > 0) { Person person = GetPerson(personId); if (person != null) { name = person.Name; tel = person.Tel; } } string title = personId > 0 ? "Edit Person" : "Add New Person"; return WrapHtml(title, $@"

{title}

Name:

Tel:

Cancel

"); } // --- Save (insert or update) --- static string SaveData(HttpRequest request) { int personId = request.GetFormInt("personid"); string name = request.GetForm("name"); string tel = request.GetForm("tel"); if (personId > 0) { // Update existing Person person = GetPerson(personId); if (person != null) { person.Name = name; person.Tel = tel; } } else { // Insert new People.Add(new Person { Id = NextId++, Name = name, Tel = tel }); } // Redirect back to list (POST-Redirect-GET pattern) return ListData(request); } // --- Delete --- static string DeleteData(HttpRequest request) { int personId = request.GetFormInt("personid"); People.RemoveAll(p => p.Id == personId); return ListData(request); } // --- About page --- static string HandleAbout(HttpRequest request) { return WrapHtml("About", @"

About

This is a simple web server built with pure C# console application.

No ASP.NET. No Kestrel. No framework.

Just a TcpListener, raw HTTP parsing, and your code.

Go to Home

"); } // --- 404 --- static string HandleNotFound(HttpRequest request) { return WrapHtml("Not Found", @"

404 - Page Not Found

The path you requested does not exist.

Go to Home

"); } // ========================================================== // Utility // ========================================================== static Person GetPerson(int id) { foreach (var p in People) { if (p.Id == id) return p; } return null; } static string HtmlEncode(string text) { if (string.IsNullOrEmpty(text)) return ""; return text.Replace("&", "&") .Replace("<", "<") .Replace(">", ">") .Replace("\"", """) .Replace("'", "'"); } static string WrapHtml(string title, string bodyContent) { return $@" {HtmlEncode(title)} {bodyContent} "; } } // ========================================================== // Models // ========================================================== class Person { public int Id { get; set; } public string Name { get; set; } public string Tel { get; set; } } // ========================================================== // HTTP Request Model // This is what Kestrel builds internally. // Here we build it ourselves from raw bytes. // ========================================================== class HttpRequest { public string Method { get; set; } = "GET"; public string Path { get; set; } = "/"; public Dictionary Headers { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Query { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Form { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public string GetForm(string key) { return Form.ContainsKey(key) ? Form[key] : ""; } public int GetFormInt(string key) { string val = GetForm(key); int result; int.TryParse(val, out result); return result; } public string GetQuery(string key) { return Query.ContainsKey(key) ? Query[key] : ""; } }