A http server, implemented in C#. Originally built for the /r/dailyprogrammer hard challenge #322.

HttpServer.cs 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MimeSharp;
  9. namespace SBRL.GlidingSquirrel
  10. {
  11. public abstract class HttpServer
  12. {
  13. public static readonly string Version = "0.1-alpha";
  14. public readonly IPAddress BindAddress;
  15. public readonly int Port;
  16. public string BindEndpoint {
  17. get {
  18. string result = BindAddress.ToString();
  19. if(result.Contains(":"))
  20. result = $"[{result}]";
  21. result += $":{Port}";
  22. return result;
  23. }
  24. }
  25. protected TcpListener server;
  26. private Mime mimeLookup = new Mime();
  27. public Dictionary<string, string> MimeTypeOverrides = new Dictionary<string, string>() {
  28. [".html"] = "text/html"
  29. };
  30. public HttpServer(IPAddress inBindAddress, int inPort)
  31. {
  32. BindAddress = inBindAddress;
  33. Port = inPort;
  34. }
  35. public HttpServer(int inPort) : this(IPAddress.IPv6Any, inPort)
  36. {
  37. }
  38. public async Task Start()
  39. {
  40. Log.WriteLine($"GlidingSquirrel v{Version}");
  41. Log.Write("Starting server - ");
  42. server = new TcpListener(new IPEndPoint(BindAddress, Port));
  43. server.Start();
  44. Console.WriteLine("done");
  45. Log.WriteLine($"Listening for requests on http://{BindEndpoint}");
  46. await setup();
  47. while(true)
  48. {
  49. TcpClient nextClient = await server.AcceptTcpClientAsync();
  50. ThreadPool.QueueUserWorkItem(new WaitCallback(HandleClientThreadRoot), nextClient);
  51. }
  52. }
  53. /// <summary>
  54. /// Fetches the mime type for a given file path.
  55. /// This method applies
  56. /// </summary>
  57. /// <returns>The mime type for the specified file path.</returns>
  58. /// <param name="filePath">The file path to lookup.</param>
  59. public string LookupMimeType(string filePath)
  60. {
  61. string fileExtension = Path.GetExtension(filePath);
  62. if(MimeTypeOverrides.ContainsKey(fileExtension))
  63. return MimeTypeOverrides[fileExtension];
  64. return mimeLookup.Lookup(filePath);
  65. }
  66. /// <summary>
  67. /// Handles requests from a specified client.
  68. /// This is the root method that is called when spawning a new thread to
  69. /// handle the client.
  70. /// </summary>
  71. /// <param name="transferredClient">The client to handle.</param>
  72. protected async void HandleClientThreadRoot(object transferredClient)
  73. {
  74. TcpClient client = transferredClient as TcpClient;
  75. try
  76. {
  77. await HandleClient(client);
  78. }
  79. catch(Exception error)
  80. {
  81. Console.WriteLine(error);
  82. }
  83. finally
  84. {
  85. client.Close();
  86. }
  87. }
  88. public async Task HandleClient(TcpClient client)
  89. {
  90. StreamReader source = new StreamReader(client.GetStream());
  91. StreamWriter destination = new StreamWriter(client.GetStream()) { AutoFlush = true };
  92. HttpRequest request = await HttpRequest.FromStream(source);
  93. request.ClientAddress = client.Client.RemoteEndPoint as IPEndPoint;
  94. HttpResponse response = new HttpResponse();
  95. response.Headers.Add("server", $"GlidingSquirrel/{Version}");
  96. try
  97. {
  98. await HandleRequest(request, response);
  99. }
  100. catch(Exception error)
  101. {
  102. response.ResponseCode = new HttpResponseCode(503, "Server Error Occurred");
  103. await response.SetBody(
  104. $"An error ocurred whilst serving your request to '{request.Url}'. Details:\n\n" +
  105. $"{error.ToString()}"
  106. );
  107. }
  108. Log.WriteLine(
  109. "{0} [{1}] [{2}] {3}",
  110. request.ClientAddress,
  111. request.Method.ToString(),
  112. response.ResponseCode,
  113. request.Url
  114. );
  115. await response.SendTo(destination);
  116. client.Close();
  117. }
  118. protected abstract Task setup();
  119. public abstract Task HandleRequest(HttpRequest request, HttpResponse response);
  120. }
  121. }