Brick icon

HttpServer.cs

Brick | PRO | 09/30/15 11:50:45 PM UTC | 0 ⭐ | 263 👁️ | Never ⏰ | []
C# |

12.07 KB

|

None

|

0 👍

/

0 👎

#region
 
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using MimeTypes;
using MyServer.Properties;
 
#endregion
 
namespace MyServer
{
    internal class HttpServer : IDisposable
    {
        private const int Kb = 1024;
        private const int Mb = Kb*Kb;
        private const int PacketSize = Mb*4; // Buffer size for reading from files.
        private readonly string _fileDirectory;
        private readonly HttpListener _httpListener;
        private Thread _listenerThread;
        private State _serverState;
 
        public HttpServer(string rootFolder = @"Files\", bool relative = true)
        {
            rootFolder = rootFolder.EndsWith("\\") ? rootFolder : rootFolder + "\\";
            _serverState = State.Stopped;
            var serverDirectory = AppDomain.CurrentDomain.BaseDirectory;
 
            _fileDirectory = relative ? Path.Combine(serverDirectory, rootFolder) : rootFolder;
            _listenerThread = new Thread(ListenerThread);
 
            Log($"Server Directory: {serverDirectory}");
            Log($"File Directory: {_fileDirectory}");
 
            _httpListener = new HttpListener();
            _httpListener.Prefixes.Add(@"http://127.0.0.1:80/");
        }
 
        public void Dispose()
        {
            _httpListener.Close();
        }
 
        private void ListenerThread()
        {
            while (_httpListener.IsListening)
            {
                try
                {
                    var context = _httpListener.GetContext();
                    ThreadPool.QueueUserWorkItem(HandleRequest, context);
                }
                catch (Exception e)
                {
                    Log($"[Error] {e.Message}");
                }
                GC.Collect();
            }
        }
 
        public void Start()
        {
            if (_serverState == State.Aborted) return;
            if (!_httpListener.IsListening)
                _httpListener.Start();
            try
            {
                if (!_listenerThread.IsAlive)
                    _listenerThread = new Thread(ListenerThread);
                _listenerThread.Start();
                
            }
            catch (Exception e)
            {
                Log(e.Message);
            }
            _serverState = State.Running;
 
            Log("Starting Server.");
        }
 
        public void Stop()
        {
            if (_serverState == State.Aborted) return;
            if (_httpListener.IsListening)
                _httpListener.Stop();
            _serverState = State.Stopped;
 
            Log("Stopping Server.");
        }
 
        public void Abort()
        {
            if (_serverState == State.Aborted) return;
            _listenerThread.Abort();
            _httpListener.Abort();
            _serverState = State.Aborted;
 
            Log("Aborting Server.");
        }
 
        private static bool TryGetStream(string path, out Stream stream)
        {
            try
            {
                if (File.Exists(path))
                {
                    stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
                    return true;
                }
                stream = Stream.Null;
                return false;
            }
            catch (Exception e)
            {
                Log(e.Message);
                stream = Stream.Null;
                return false;
            }
        }
 
        private static bool TryGetDirectory(string directory, out DirectoryInfo info)
        {
            try
            {
                info = new DirectoryInfo(directory);
                return info.Exists;
            }
            catch (Exception e)
            {
                Log(e.Message);
                info = new DirectoryInfo(string.Empty);
                return false;
            }
        }
 
        private string ToUrl(string localPath)
        {
            if (localPath == null) return string.Empty;
            if (!localPath.StartsWith(_fileDirectory, StringComparison.CurrentCultureIgnoreCase)) return string.Empty;
            if (_fileDirectory.Equals(localPath, StringComparison.CurrentCultureIgnoreCase)) return string.Empty;
            if (localPath.Length < _fileDirectory.Length) return string.Empty;
 
            return HttpUtility.HtmlEncode(localPath.Substring(_fileDirectory.Length));
        }
 
        private static void Log(object data)
        {
            Console.WriteLine($"{DateTime.Now:G} | {data}");
        }
 
        private enum State
        {
            Running,
            Stopped,
            Aborted
        }
 
        #region RequestHandler
 
        private static readonly Regex RangeRegex = new Regex(@"bytes=?(?<start>\d+)?-(?<end>\d+)?",
            RegexOptions.Compiled);
 
        private void HandleRequest(object state)
        {
            var context = (HttpListenerContext) state;
            try
            {
                var request = context.Request;
                var response = context.Response;
                var host = request.UserHostName;
                var localRequest = HttpUtility.HtmlDecode(request.Url.LocalPath).Replace('/', '\\').Substring(1);
                var requestedPath = Path.Combine(_fileDirectory, localRequest);
                var outputStream = response.OutputStream;
                Log($@"[Request] Path: \{localRequest}");
 
                Stream stream;
                DirectoryInfo directoryInfo;
                var bytesToSend = new byte[0];
                if (localRequest.Equals("favicon.ico"))
                {
                    var iconBytes = Resources.favicon;
                    response.KeepAlive = false;
                    response.ContentType = "image/x-icon";
                    response.Headers.Add("Content-Type", "image/x-icon"); // Ask the system for the filetype.
                    response.Headers.Add("Content-Disposition", "inline; filename=\"favicon.ico\"");
                    response.Headers.Add("Date", $"{DateTime.Now:R}");
                    response.ContentLength64 = iconBytes.LongLength;
                    outputStream.Write(iconBytes, 0, iconBytes.Length);
                }
                else if (TryGetStream(requestedPath, out stream))
                {
                    var fileInfo = new FileInfo(requestedPath);
                    var fileName = fileInfo.Name;
                    var fileExtension = fileInfo.Extension;
                    var contentType = MimeTypeMap.GetMimeType(fileExtension);
                    var totalLength = stream.Length;
 
                    var range = request.Headers["Range"] ?? string.Empty;
                    var match = RangeRegex.Match(range);
                    var start = match.Groups["start"].Success ? long.Parse(match.Groups["start"].Value) : 0L;
                    var finish = match.Groups["end"].Success ? long.Parse(match.Groups["end"].Value) : totalLength;
 
                    response.KeepAlive = false;
                    response.ContentType = contentType;
                    response.Headers.Add("Content-Type", contentType); // Ask the system for the filetype.
                    response.Headers.Add("Content-Disposition", $"inline; filename={fileName}");
                    response.Headers.Add("Date", $"{DateTime.Now:R}");
                    response.Headers.Add("Last-Modified", $"{fileInfo.LastWriteTime:R}");
                    response.Headers.Add("Accept-Ranges", "bytes");
                    response.Headers.Add("Content-Range", $"bytes {start}-{finish - 1}/{totalLength}");
                    if (start == 0 && finish == totalLength)
                    {
                        response.StatusCode = 200;
                    }
                    else if(start >= 0 && finish <= totalLength)
                    {
                        response.StatusCode = 206;
                    }
                    else
                    {
                        response.StatusCode = 416;
                        throw new Exception("Request range out of bounds.");
                    }
                    response.ContentLength64 = finish - start;
 
 
                    stream.Seek(start, SeekOrigin.Begin);
                    for (var i = 0; i < finish - start; i += PacketSize)
                    {
                        var buffer = new byte[PacketSize];
                        var bytes = stream.Read(buffer, 0, PacketSize);
                        var bytesToWrite = (int) Math.Min(finish - start - i, bytes);
 
                        outputStream.Write(buffer, 0, bytesToWrite);
                    }
                    stream.Close();
                }
                else if (TryGetDirectory(requestedPath, out directoryInfo))
                {
                    response.KeepAlive = false;
                    response.ContentType = "text/html";
                    response.Headers.Add("Content-Type", "text/html; charset=UTF-8");
                    response.Headers.Add("Content-Language", "en");
                    response.Headers.Add("Content-Disposition", $"inline; filename={directoryInfo.Name}.html");
                    response.Headers.Add("Date", $"{DateTime.Now:R}");
                    response.Headers.Add("Last-Modified", $"{directoryInfo.LastWriteTime:R}");
                    response.StatusCode = 200;
 
 
                    var sb = new StringBuilder();
                    foreach (var directory in directoryInfo.EnumerateDirectories().OrderBy(s => s.Name))
                    {
                        sb.AppendLine(
                            $"<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>");
                    }
                    foreach (var file in directoryInfo.EnumerateFiles().OrderBy(s => s.Name))
                    {
                        sb.AppendLine(
                            $"<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>");
                    }
                    var endString = string.Format(Resources.Folder, directoryInfo.Name, ToUrl(directoryInfo.FullName),
                        host, ToUrl(directoryInfo.Parent.FullName), sb, $"{DateTime.Now:R}");
 
                    bytesToSend = Encoding.UTF8.GetBytes(endString);
                }
                else
                {
                    response.KeepAlive = false;
                    response.ContentType = "text/html";
                    response.Headers.Add("Content-Type", "text/html; charset=UTF-8");
                    response.Headers.Add("Content-Language", "en");
                    response.Headers.Add("Content-Disposition", $"inline; filename={directoryInfo.Name}.html");
                    response.Headers.Add("Date", $"{DateTime.Now:R}");
                    response.StatusCode = 200;
 
                    bytesToSend = Encoding.UTF8.GetBytes(string.Format(Resources.Error, localRequest));
                }
                if (bytesToSend.Length > 0 && outputStream.CanWrite)
                {
                    response.ContentEncoding = Encoding.UTF8;
                    response.ContentLength64 = bytesToSend.LongLength;
                    outputStream.Write(bytesToSend, 0, bytesToSend.Length);
                }
                outputStream.Flush();
                response.Close();
            }
            catch (HttpListenerException)
            {
                context.Response.Abort();
            }
            catch (Exception e)
            {
                context.Response.Abort();
                Log($"[Error] {e.Message}");
            }
        }
 
        #endregion RequestHandler
    }
}

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎