Advertisement
Brick

HttpServer.cs

Sep 30th, 2015
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 12.07 KB | None | 0 0
  1. #region
  2.  
  3. using System;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using System.Web;
  11. using MimeTypes;
  12. using MyServer.Properties;
  13.  
  14. #endregion
  15.  
  16. namespace MyServer
  17. {
  18.     internal class HttpServer : IDisposable
  19.     {
  20.         private const int Kb = 1024;
  21.         private const int Mb = Kb*Kb;
  22.         private const int PacketSize = Mb*4; // Buffer size for reading from files.
  23.         private readonly string _fileDirectory;
  24.         private readonly HttpListener _httpListener;
  25.         private Thread _listenerThread;
  26.         private State _serverState;
  27.  
  28.         public HttpServer(string rootFolder = @"Files\", bool relative = true)
  29.         {
  30.             rootFolder = rootFolder.EndsWith("\\") ? rootFolder : rootFolder + "\\";
  31.             _serverState = State.Stopped;
  32.             var serverDirectory = AppDomain.CurrentDomain.BaseDirectory;
  33.  
  34.             _fileDirectory = relative ? Path.Combine(serverDirectory, rootFolder) : rootFolder;
  35.             _listenerThread = new Thread(ListenerThread);
  36.  
  37.             Log($"Server Directory: {serverDirectory}");
  38.             Log($"File Directory: {_fileDirectory}");
  39.  
  40.             _httpListener = new HttpListener();
  41.             _httpListener.Prefixes.Add(@"http://127.0.0.1:80/");
  42.         }
  43.  
  44.         public void Dispose()
  45.         {
  46.             _httpListener.Close();
  47.         }
  48.  
  49.         private void ListenerThread()
  50.         {
  51.             while (_httpListener.IsListening)
  52.             {
  53.                 try
  54.                 {
  55.                     var context = _httpListener.GetContext();
  56.                     ThreadPool.QueueUserWorkItem(HandleRequest, context);
  57.                 }
  58.                 catch (Exception e)
  59.                 {
  60.                     Log($"[Error] {e.Message}");
  61.                 }
  62.                 GC.Collect();
  63.             }
  64.         }
  65.  
  66.         public void Start()
  67.         {
  68.             if (_serverState == State.Aborted) return;
  69.             if (!_httpListener.IsListening)
  70.                 _httpListener.Start();
  71.             try
  72.             {
  73.                 if (!_listenerThread.IsAlive)
  74.                     _listenerThread = new Thread(ListenerThread);
  75.                 _listenerThread.Start();
  76.                
  77.             }
  78.             catch (Exception e)
  79.             {
  80.                 Log(e.Message);
  81.             }
  82.             _serverState = State.Running;
  83.  
  84.             Log("Starting Server.");
  85.         }
  86.  
  87.         public void Stop()
  88.         {
  89.             if (_serverState == State.Aborted) return;
  90.             if (_httpListener.IsListening)
  91.                 _httpListener.Stop();
  92.             _serverState = State.Stopped;
  93.  
  94.             Log("Stopping Server.");
  95.         }
  96.  
  97.         public void Abort()
  98.         {
  99.             if (_serverState == State.Aborted) return;
  100.             _listenerThread.Abort();
  101.             _httpListener.Abort();
  102.             _serverState = State.Aborted;
  103.  
  104.             Log("Aborting Server.");
  105.         }
  106.  
  107.         private static bool TryGetStream(string path, out Stream stream)
  108.         {
  109.             try
  110.             {
  111.                 if (File.Exists(path))
  112.                 {
  113.                     stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  114.                     return true;
  115.                 }
  116.                 stream = Stream.Null;
  117.                 return false;
  118.             }
  119.             catch (Exception e)
  120.             {
  121.                 Log(e.Message);
  122.                 stream = Stream.Null;
  123.                 return false;
  124.             }
  125.         }
  126.  
  127.         private static bool TryGetDirectory(string directory, out DirectoryInfo info)
  128.         {
  129.             try
  130.             {
  131.                 info = new DirectoryInfo(directory);
  132.                 return info.Exists;
  133.             }
  134.             catch (Exception e)
  135.             {
  136.                 Log(e.Message);
  137.                 info = new DirectoryInfo(string.Empty);
  138.                 return false;
  139.             }
  140.         }
  141.  
  142.         private string ToUrl(string localPath)
  143.         {
  144.             if (localPath == null) return string.Empty;
  145.             if (!localPath.StartsWith(_fileDirectory, StringComparison.CurrentCultureIgnoreCase)) return string.Empty;
  146.             if (_fileDirectory.Equals(localPath, StringComparison.CurrentCultureIgnoreCase)) return string.Empty;
  147.             if (localPath.Length < _fileDirectory.Length) return string.Empty;
  148.  
  149.             return HttpUtility.HtmlEncode(localPath.Substring(_fileDirectory.Length));
  150.         }
  151.  
  152.         private static void Log(object data)
  153.         {
  154.             Console.WriteLine($"{DateTime.Now:G} | {data}");
  155.         }
  156.  
  157.         private enum State
  158.         {
  159.             Running,
  160.             Stopped,
  161.             Aborted
  162.         }
  163.  
  164.         #region RequestHandler
  165.  
  166.         private static readonly Regex RangeRegex = new Regex(@"bytes=?(?<start>\d+)?-(?<end>\d+)?",
  167.             RegexOptions.Compiled);
  168.  
  169.         private void HandleRequest(object state)
  170.         {
  171.             var context = (HttpListenerContext) state;
  172.             try
  173.             {
  174.                 var request = context.Request;
  175.                 var response = context.Response;
  176.                 var host = request.UserHostName;
  177.                 var localRequest = HttpUtility.HtmlDecode(request.Url.LocalPath).Replace('/', '\\').Substring(1);
  178.                 var requestedPath = Path.Combine(_fileDirectory, localRequest);
  179.                 var outputStream = response.OutputStream;
  180.                 Log($@"[Request] Path: \{localRequest}");
  181.  
  182.                 Stream stream;
  183.                 DirectoryInfo directoryInfo;
  184.                 var bytesToSend = new byte[0];
  185.                 if (localRequest.Equals("favicon.ico"))
  186.                 {
  187.                     var iconBytes = Resources.favicon;
  188.                     response.KeepAlive = false;
  189.                     response.ContentType = "image/x-icon";
  190.                     response.Headers.Add("Content-Type", "image/x-icon"); // Ask the system for the filetype.
  191.                     response.Headers.Add("Content-Disposition", "inline; filename=\"favicon.ico\"");
  192.                     response.Headers.Add("Date", $"{DateTime.Now:R}");
  193.                     response.ContentLength64 = iconBytes.LongLength;
  194.                     outputStream.Write(iconBytes, 0, iconBytes.Length);
  195.                 }
  196.                 else if (TryGetStream(requestedPath, out stream))
  197.                 {
  198.                     var fileInfo = new FileInfo(requestedPath);
  199.                     var fileName = fileInfo.Name;
  200.                     var fileExtension = fileInfo.Extension;
  201.                     var contentType = MimeTypeMap.GetMimeType(fileExtension);
  202.                     var totalLength = stream.Length;
  203.  
  204.                     var range = request.Headers["Range"] ?? string.Empty;
  205.                     var match = RangeRegex.Match(range);
  206.                     var start = match.Groups["start"].Success ? long.Parse(match.Groups["start"].Value) : 0L;
  207.                     var finish = match.Groups["end"].Success ? long.Parse(match.Groups["end"].Value) : totalLength;
  208.  
  209.                     response.KeepAlive = false;
  210.                     response.ContentType = contentType;
  211.                     response.Headers.Add("Content-Type", contentType); // Ask the system for the filetype.
  212.                     response.Headers.Add("Content-Disposition", $"inline; filename={fileName}");
  213.                     response.Headers.Add("Date", $"{DateTime.Now:R}");
  214.                     response.Headers.Add("Last-Modified", $"{fileInfo.LastWriteTime:R}");
  215.                     response.Headers.Add("Accept-Ranges", "bytes");
  216.                     response.Headers.Add("Content-Range", $"bytes {start}-{finish - 1}/{totalLength}");
  217.                     if (start == 0 && finish == totalLength)
  218.                     {
  219.                         response.StatusCode = 200;
  220.                     }
  221.                     else if(start >= 0 && finish <= totalLength)
  222.                     {
  223.                         response.StatusCode = 206;
  224.                     }
  225.                     else
  226.                     {
  227.                         response.StatusCode = 416;
  228.                         throw new Exception("Request range out of bounds.");
  229.                     }
  230.                     response.ContentLength64 = finish - start;
  231.  
  232.  
  233.                     stream.Seek(start, SeekOrigin.Begin);
  234.                     for (var i = 0; i < finish - start; i += PacketSize)
  235.                     {
  236.                         var buffer = new byte[PacketSize];
  237.                         var bytes = stream.Read(buffer, 0, PacketSize);
  238.                         var bytesToWrite = (int) Math.Min(finish - start - i, bytes);
  239.  
  240.                         outputStream.Write(buffer, 0, bytesToWrite);
  241.                     }
  242.                     stream.Close();
  243.                 }
  244.                 else if (TryGetDirectory(requestedPath, out directoryInfo))
  245.                 {
  246.                     response.KeepAlive = false;
  247.                     response.ContentType = "text/html";
  248.                     response.Headers.Add("Content-Type", "text/html; charset=UTF-8");
  249.                     response.Headers.Add("Content-Language", "en");
  250.                     response.Headers.Add("Content-Disposition", $"inline; filename={directoryInfo.Name}.html");
  251.                     response.Headers.Add("Date", $"{DateTime.Now:R}");
  252.                     response.Headers.Add("Last-Modified", $"{directoryInfo.LastWriteTime:R}");
  253.                     response.StatusCode = 200;
  254.  
  255.  
  256.                     var sb = new StringBuilder();
  257.                     foreach (var directory in directoryInfo.EnumerateDirectories().OrderBy(s => s.Name))
  258.                     {
  259.                         sb.AppendLine(
  260.                             $"<tr><td class=\"name\"><a href=\"//{host}/{ToUrl(directory.FullName)}/\">/{directory.Name}/</a></td><td class=\"date\">{directory.LastWriteTime:G}</td><td class=\"size\">{directory.EnumerateFiles("*", SearchOption.AllDirectories).Sum(s => s.Length)/Kb} KB</td><tr>");
  261.                     }
  262.                     foreach (var file in directoryInfo.EnumerateFiles().OrderBy(s => s.Name))
  263.                     {
  264.                         sb.AppendLine(
  265.                             $"<tr><td class=\"name\"><a href=\"//{host}/{ToUrl(file.FullName)}\">/{file.Name}</a></td><td class=\"date\">{file.LastWriteTime:G}</td><td class=\"size\">{file.Length/Kb} KB</td></tr>");
  266.                     }
  267.                     var endString = string.Format(Resources.Folder, directoryInfo.Name, ToUrl(directoryInfo.FullName),
  268.                         host, ToUrl(directoryInfo.Parent.FullName), sb, $"{DateTime.Now:R}");
  269.  
  270.                     bytesToSend = Encoding.UTF8.GetBytes(endString);
  271.                 }
  272.                 else
  273.                 {
  274.                     response.KeepAlive = false;
  275.                     response.ContentType = "text/html";
  276.                     response.Headers.Add("Content-Type", "text/html; charset=UTF-8");
  277.                     response.Headers.Add("Content-Language", "en");
  278.                     response.Headers.Add("Content-Disposition", $"inline; filename={directoryInfo.Name}.html");
  279.                     response.Headers.Add("Date", $"{DateTime.Now:R}");
  280.                     response.StatusCode = 200;
  281.  
  282.                     bytesToSend = Encoding.UTF8.GetBytes(string.Format(Resources.Error, localRequest));
  283.                 }
  284.                 if (bytesToSend.Length > 0 && outputStream.CanWrite)
  285.                 {
  286.                     response.ContentEncoding = Encoding.UTF8;
  287.                     response.ContentLength64 = bytesToSend.LongLength;
  288.                     outputStream.Write(bytesToSend, 0, bytesToSend.Length);
  289.                 }
  290.                 outputStream.Flush();
  291.                 response.Close();
  292.             }
  293.             catch (HttpListenerException)
  294.             {
  295.                 context.Response.Abort();
  296.             }
  297.             catch (Exception e)
  298.             {
  299.                 context.Response.Abort();
  300.                 Log($"[Error] {e.Message}");
  301.             }
  302.         }
  303.  
  304.         #endregion RequestHandler
  305.     }
  306. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement