Click here to Skip to main content
15,886,757 members
Articles / Programming Languages / C#

C# WebServer Using Sockets

Rate me:
Please Sign up or sign in to vote.
5.00/5 (27 votes)
23 Jan 2014CPOL7 min read 103.6K   9.9K   161  
How to make a simple web server which supports GZIP compression, applications, and sessions.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;

using ServerCommonLibrary;
using BizApplication.http;



namespace BizApplication.Http
{
    /// <summary>
    /// 
    /// HttpApplicationManager generate and keep alive sessions checking how many of them are expired, 
    /// contains method for validate an http request and determinates which application match with the request.
    ///     
    ///    
    /// </summary>
    public class HttpApplicationManager : IDisposable
    {

        //### Current server domain
        public static string CurrentDomain;
        //### Physical directory
        public static string RootDirectory;
        //### Default page
        public static string DefaultPage;
        //### Service Port
        public static int ServicePort;

        //### Session Dictionary  Key: ApplicationUniqueName/ Value: SessionManager
        ConcurrentDictionary<string, SessionManager> applications;

        //### Timer 
        Timer managerTimer;

        public HttpApplicationManager()
        {
            applications = new ConcurrentDictionary<string, SessionManager>();
            managerTimer = new Timer(ManagerTimerMain, applications, 1000, 1000);
        }

        /// <summary>
        /// Try to add an empty session manager 
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public bool AddHttpApplication(SessionManager app)
        {
            if (applications.ContainsKey(app.Info.UniqueApplicationName)) 
                return false;
            return applications.TryAdd(app.Info.UniqueApplicationName, app);
        }

        /// <summary>
        /// The timer periodically check expired sessions 
        /// </summary>
        /// <param name="e"></param>
        protected void ManagerTimerMain(object e)
        {
            ConcurrentDictionary<string, SessionManager> manager = (ConcurrentDictionary<string, SessionManager>)e;
            foreach (var sessionMgr in manager.Values)
            {                
                //### check expired session
                sessionMgr.UnloadExpiredInstances();
            }
        }

        /// <summary>
        /// Return an application istance that correspond with the request.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="application"></param>
        /// <returns>false if not exist</returns>
        public bool TryGetApplicationInstance(HttpRequest e, out ApplicationInstanceBase application)
        {
            ///
            /// The logic to get the right application from a request is very simple,
            /// For identify the application we look the main path in the url, is the first after the domain domain.xxx ex:
            /// http://domain.xxc/maindir/dir1/dir2 so, dir1/dir2 are internal paths relatively at the main path maindir.
            /// The rule is the main path indicate the name of the application, so
            /// http://localhost:8080/chat/index.html identify the chat application 
            /// http://localhost:8080/chat/chat2/index.html identify the chat application 
            ///             
            ///

            application = null;
            
            string[] paths = e.Path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
           
            if (paths.Length == 0) return false;
           
            string mainPath = paths[0];
            ///
            /// Immediately check if exist an ApplicationUniqueName equal to mainpath
            ///
            if (!applications.ContainsKey(mainPath)) return false;
            //Ok the applicatoin Exist!
            
            SessionManager sessionMgr = applications[mainPath];
            
            ApplicationSettings settings = sessionMgr.Info;
            string sessionKey = string.Empty;
            ///
            /// The SessionManager is found now we check if already exist any session
            ///
            switch (settings.SessionType)
            {
                case ApplicationSessionMode.SingletonSession:
                    ///
                    /// SingletonSession 
                    ///
                    application = sessionMgr.GetOrCreateSingletonInstance();
                    return true;
                case ApplicationSessionMode.BrowserSession:
                    ///
                    /// We need a session key to identfy one particolare browser 
                    ///                    
                    sessionKey = e.Rawrequest.Connection.IP + "@" + e.Requests["User-Agent"];
                    break;
                case ApplicationSessionMode.IpSession:
                    ///
                    /// We need a session key to identfy one Ip address
                    ///                    
                    sessionKey = e.Rawrequest.Connection.IP.ToString();
                    break;
            }
           
            application = sessionMgr.GetOrCreateInstanceBySessionKey(sessionKey);

            return true;
        }

        /// <summary>
        /// Dispatch a response to the other sessions.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <param name="req"></param>
        public void ShareApplicationOutput(ApplicationInstanceBase sender, ApplicationResponse e, HttpRequest req)
        {
            //### verify if application exist
            if (applications.ContainsKey(sender.Info.UniqueApplicationName))
            {
                //### Session manager cointains all instances
                SessionManager session = applications[sender.Info.UniqueApplicationName];                
                foreach (var item in session.SessionList)
                {
                    //### Skip the sender
                    if (item.Value.ApplicationId != sender.ApplicationId)
                    {
                        item.Value.OnNewShareResponse(sender, e, req);
                    }
                }
            }
        }

        /// <summary>
        /// This method try to find a resource in a ResourceDirectory path and return a response.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public ApplicationResponse ResponseStaticResource(HttpRequest request)
        {
            string resource = request.Path;
            ApplicationResponse output = null;
            if (resource[0] != '/') resource = "/" + resource;
            //Get resource mime type
            MimeType type = HttpHelper.GetResourceMime(resource);
            //Check if Gzip compression is enable
            bool gzip = request.isGZIPSupported();
            //Get a file
            byte[] file = Helper.GetFile(RootDirectory + request.Path);
            if (file != null)
            {
                //compress data id Gzip is supported
                file = gzip ? HttpHelper.CompressGZIP(file) : file;
                //get a pageHeader
                byte[] header = HttpHelper.GetHeader(file.Length, type, true, gzip);
                //build the complete response
                byte[] response = header.Concat(file);
                //create the response
                output = new HttpResponse(response, request);
            }
            else
            {
                //file not found: return 404 response
                output = new HttpResponse(HttpHelper.GetHtml404Header(0, type), request);
            }
            return output;
        }

        /// <summary>
        /// This function check if the RawRequest be received from the browser is well formed.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="req"></param>
        /// <returns></returns>
        public static bool TryValidate(RawRequest e, out HttpRequest req)
        {
            HttpRequestType request = HttpRequestType.HttpPage;
            try
            {
                ///
                /// There are a lot of tecnique to extract the information in an http browser request,
                /// this solutions split the request string and analize each blocks.
                /// 

                req = new HttpRequest(e);
                ///
                ///Decode the bytes in Utf8 chars and Split the string request by "\r\n" 
                ///
                req.CompleteRequest = new String(Encoding.UTF8.GetChars(e.RawData));
                
                string[] groups = req.CompleteRequest.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

                for (int i = 0; i < groups.Length; i++)
                {
                    string headerblock = groups[i];

                    if (i > 0 && headerblock.Contains(":"))
                    {
                        //From the second block we have fileds with the pattern <name:value>
                        string[] block = headerblock.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries);
                        req.Requests.Add(block[0], block[1]);
                    }
                    else
                    {
                        ///
                        /// The first block always include the request path ,the method and the protocol http.
                        /// ex. GET /path/resource.ext HTTP/1.1 
                        /// 

                        
                        string[] subparts = headerblock.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                        //copy the path 
                        req.CompletePath = subparts[1];                        
                        if (subparts[0] == "GET") req.Method = HttpMethodType.Get;
                        if (subparts[0] == "POST") req.Method = HttpMethodType.Post;                        
                        if (!String.IsNullOrEmpty(subparts[1]))
                        {
                            //split the path in "directories"
                            string[] resourcePaths = subparts[1].Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
                            if (resourcePaths.Length > 0)
                            {
                                ///
                                /// We make a distinction between HttpStaticRequest and HttpPage 
                                ///
                                
                                string resourceFullName = resourcePaths[resourcePaths.Length - 1];
                                
                                if (HttpHelper.IsStaticResource(resourceFullName))
                                {
                                    request = HttpRequestType.HttpStaticRequest;
                                }
                            }
                        }
                        else throw new InvalidOperationException("Invalid Request : " + req.CompleteRequest);

                        ///
                        /// separate the request path from possibly query-url 
                        /// 
                        string[] subPaths = subparts[1].Split(new string[] { "?" }, StringSplitOptions.RemoveEmptyEntries);
                        
                        req.Path = subPaths[0];                        
                        req.Paths = subPaths[0].Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries).ToList();
                    }
                }

                //Save the query url parts in a dictionary : req.UrlParameters
                string[] queryparams = HttpHelper.GetUrlQueries(HttpHelper.RemoveToken(req.CompletePath));
                foreach (string p in queryparams)
                {
                    string[] query = p.Split(new String[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
                    if (query.Length == 2)
                    {
                        req.UrlParameters.Add(query[0], query[1]);
                    }
                }
            }
            catch (Exception)
            {
                //If somethig goes wrong the validation return false.
                req = null;
                return false;
            }
            req.Type = request;
            return true;
        }

        public void Dispose()
        {
            if (managerTimer != null)
            {
                managerTimer.Change(-1, -1);
                managerTimer.Dispose();
                managerTimer = null;
            }
        }


    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Alberto Biafelli,
Software Developer

Comments and Discussions