Avancerad cykelbyggnad eller klient-serverapplikation baserad pÄ C# .Net framework

Inledning

Allt började med att en kollega föreslog att jag skulle skapa en liten webbservice. Det skulle bli nÄgot liknande Tinder, men för IT-gemenskapen. Funktionaliteten Àr otroligt enkel: du registrerar dig, fyller i din profil och gÄr vidare till kÀrnan av tjÀnsten, nÀmligen att hitta samtalspartner och utöka ditt nÀtverk samt fÄ nya bekantskaper.

HÀr mÄste jag ta ett steg tillbaka och berÀtta lite om mig sjÀlv, sÄ att det senare blir tydligare varför jag har tagit just dessa steg i utvecklingen.

För nÀrvarande arbetar jag som Teknisk Artist pÄ en speldstudio. Min erfarenhet av programmering i C# har enbart byggt pÄ att skriva skript och verktyg för Unity, samt skapa plugins för lÄg-nivÄ arbete med Android-enheter. Jag har Ànnu inte vÄgat mig utanför denna vÀrld, och nu dök den hÀr möjligheten upp.

Del 1. Prototyping av ramen

NÀr jag bestÀmt mig för hur tjÀnsten skulle se ut, började jag leta efter alternativ för genomförande. Det skulle vara enklast att hitta en fÀrdig lösning som vi kunde anpassa vÄra mekaniker till och publicera för allmÀn kritik.
Men det hÀr var inte intressant, jag sÄg ingen utmaning eller mening i det, sÄ jag började studera webbteknologier och metoder för interaktion med dem.

Jag började med att lÀsa artiklar och dokumentation om C# .Net. HÀr hittade jag olika sÀtt att lösa uppgiften. Det finns mÄnga mekanismer för nÀtverksinteraktion, frÄn fullstÀndiga lösningar som ASP.Net eller Azure-tjÀnster, till direkt interaktion med TcpHttp-anslutningar.

Efter mitt första försök med ASP avfÀrdade jag det snabbt; jag tyckte att det var en alltför tung lösning för vÄr tjÀnst. Vi skulle inte ens anvÀnda en tredjedel av plattformens möjligheter, sÄ jag fortsatte letandet. Valet stod mellan TCP och Http-klient/server. flermarkserver, efter att ha samlat och testat vilken, beslutade jag att stanna vid interaktionen med TCP-anslutningar, av nÄgon anledning trodde jag att http inte skulle tillÄta mig att skapa en plattformsoberoende lösning.

Den första versionen av servern inkluderade hantering av anslutningar, levererade statiskt innehÄll pÄ webb sidor och hade en databas för anvÀndare. För att börja bestÀmde jag mig för att bygga funktionalitet för webbplatsen, sÄ att jag senare skulle kunna integrera hantering av applikationen pÄ Android och iOS.

HÀr Àr lite kod
HuvudtrÄden, som i en oÀndlig loop tar emot klienter:

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace ClearServer
{

    class Server
    {
        TcpListener Listener;
        public Server(int Port)
        {
            Listener = new TcpListener(IPAddress.Any, Port);
            Listener.Start();

            while (true)
            {
                TcpClient Client = Listener.AcceptTcpClient();
                Thread Thread = new Thread(new ParameterizedThreadStart(ClientThread));
                Thread.Start(Client);
            }
        }

        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (Listener != null)
            {
                Listener.Stop();
            }
        }

        static void Main(string[] args)
        {
            DatabaseWorker sqlBase = DatabaseWorker.GetInstance;

            new Server(80);
        }
    }
}

SjÀlva klienthanteraren:

anvÀnda System;
anvÀnda System.IO;
anvÀnda System.Net.Sockets;
anvÀnda System.Text;
anvÀnda System.Text.RegularExpressions;

namespace ClearServer
{
    class Client
    {


        public Client(TcpClient Client)
        {

            string Meddelande = "";
            byte[] Buffer = new byte[1024];
            int Antal;
            while ((Antal = Client.GetStream().Read(Buffer, 0, Buffer.Length)) > 0)
            {
                Meddelande += Encoding.UTF8.GetString(Buffer, 0, Antal);

                if (Meddelande.IndexOf("rnrn") >= 0 || Meddelande.Length > 4096)
                {
                    Console.WriteLine(Meddelande);
                    break;
                }
            }

            Match ReqMatch = Regex.Match(Meddelande, @"^w+s+([^s?]+)[^s]*s+HTTP\/.*|");
            if (ReqMatch == Match.Empty)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            string RequestUri = ReqMatch.Groups[1].Value;
            RequestUri = Uri.UnescapeDataString(RequestUri);
            if (RequestUri.IndexOf("..") >= 0)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            if (RequestUri.EndsWith("/"))
            {
                RequestUri += "index.html";
            }

            string FilvÀg =

"D:/Web/TestSite{RequestUri}";

if (!File.Exists(FilvÀg)
{
ErrorWorker.SendError(Client, 404);
return;
}

string TillÀgg = RequestUri.Substring(RequestUri.LastIndexOf('.'));

string InnehÄllstyp = "";

switch (TillÀgg)
{
case ".htm":
case ".html":
InnehÄllstyp = "text/html";
break;
case ".css":
InnehÄllstyp = "text/css";
break;
case ".js":
InnehÄllstyp = "text/javascript";
break;
case ".jpg":
InnehÄllstyp = "image/jpeg";
break;
case ".jpeg":
case ".png":
case ".gif":
InnehÄllstyp =

"image/{TillÀgg.Substring(1)}";
break;
default:
if (TillÀgg.Length > 1)
{
InnehÄllstyp =

"application/{TillÀgg.Substring(1)}";
}
else
{
InnehÄllstyp = "application/unknown";
}
break;
}

FileStream FS;
försök
{
FS = new FileStream(FilvÀg, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
ErrorWorker.SendError(Client, 500);
return;
}

string Headers =

"HTTP/1.1 200 OKnContent-Type: {InnehÄllstyp}nContent-Length: {FS.Length}nn";
byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);

while (FS.Position < FS.Length)
{
Antal = FS.Read(Buffer, 0, Buffer.Length);
Client.GetStream().Write(Buffer, 0, Antal);
}

FS.Close();
Client.Close();
}
}
}


Och den första databasen byggd pÄ local SQL:
using System;
uusing System.Data.Linq;
namespace ClearServer
{
    class DatabaseWorker
    {

        private static DatabaseWorker instance;

        public static DatabaseWorker GetInstance
        {
            get
            {
                if (instance == null)
                    instance = new DatabaseWorker();
                return instance;
            }
        }


        private DatabaseWorker()
        {
            string connectionStr = databasePath;
            using (DataContext db = new DataContext(connectionStr))
            {
                Table users = db.GetTable();
                foreach (var item in users)
                {
                    Console.WriteLine("{item.login} {item.password}");
                }
            }
        }
    }
}

"{item.login} {item.password}"
}
}
}
}
}


Som man kan se, skiljer sig den hÀr versionen lite frÄn den som beskrevs i artikeln. I grund och botten har det bara lagts till laddning av sidor frÄn mappen pÄ datorn och databasen (som förresten inte fungerade i denna version pÄ grund av felaktig anslutningsarkitektur).

Kapitel 2. Integrera hjul

Efter att ha testat serverns funktion beslutade jag mig för att detta skulle vara en utmÀrkt lösning (spoiler: nej), för vÄr tjÀnst, sÄ projektet började vÀxa i logik.
Steg för steg började nya moduler dyka upp och serverns funktionalitet vÀxte. Servern fick en testdomÀn och ssl kryptering av anslutningen.

Lite mer kod som beskriver serverns logik och hantering av klienter
Uppdaterad version av servern som inkluderar anvÀndning av certifikat.

using System;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Policy;
using System.Threading;


namespace ClearServer
{

    sealed class Server
    {
        readonly bool ServerRunning = true;
        readonly TcpListener sslListner;
        public static X509Certificate serverCertificate = null;
        Server()
        {
            serverCertificate = X509Certificate.CreateFromSignedFile(@"C:sslitinder.online.crt");
            sslListner = new TcpListener(IPAddress.Any, 443);
            sslListner.Start();
            Console.WriteLine("Starting server.." + serverCertificate.Subject + "n" + Assembly.GetExecutingAssembly().Location);
            while (ServerRunning)
            {
                TcpClient SslClient = sslListner.AcceptTcpClient();
                Thread SslThread = new Thread(new ParameterizedThreadStart(ClientThread));
                SslThread.Start(SslClient);
            }
            
        }
        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (sslListner != null)
            {
                sslListner.Stop();
            }
        }

        public static void Main(string[] args)
        {
            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                Console.WriteLine("Switching another domain");
                new AppDomainSetup
                {
                    ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
                };
                var current = AppDomain.CurrentDomain;
                var strongNames = new StrongName[0];
                var domain = AppDomain.CreateDomain(
                    "ClearServer", null,
                    current.SetupInformation, new PermissionSet(PermissionState.Unrestricted),
                    strongNames);
                domain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);
            }
            new Server();
        }
    }
}

Och en ny klienthandlÀggare med SSL-autentisering:

using ClearServer.Core.Requester;
using System;
using System.Net.Security;
using System.Net.Sockets;

namespace ClearServer
{
    public class Client
    {
        public Client(TcpClient Client)
        {
            SslStream SSlClientStream = new SslStream(Client.GetStream(), false);
            try
            {
                SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "---------------------------------------------------------------------n" +

"|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Autentisering misslyckades - stÀnger anslutningen.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}

}
}


Eftersom servern fungerar uteslutande med TCP-anslutningar, behövs en modul som kan kÀnna igen begÀrans sammanhang. Jag kom fram till att en parser skulle passa hÀr, som kan dela upp begÀran frÄn klienten i separata delar, med vilka jag kan interagera för att ge klienten de svar som behövs.

Parser

using ClearServer.Core.UserController;
using ReServer.Core.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer.Core.Requester
{
    public class RequestContext
    {
        public string Message = "";
        private readonly byte[] buffer = new byte[1024];
        public string RequestMethod;
        public string RequestUrl;
        public User RequestProfile;
        public User CurrentUser = null;
        public List HeadersValues;
        public List FormValues;
        private TcpClient TcpClient;

        private event Action OnRead = RequestHandler.OnHandle;

        DatabaseWorker databaseWorker = new DatabaseWorker();

        public RequestContext(SslStream ClientStream, TcpClient Client)
        {

            this.TcpClient = Client;
            try
            {
                ClientStream.BeginRead(buffer, 0, buffer.Length, ClientRead, ClientStream);
            }
            catch { return; }
        }
        private void ClientRead(IAsyncResult ar)
        {
            SslStream ClientStream = (SslStream)ar.AsyncState;

            if (ar.IsCompleted)
            {
                Message = Encoding.UTF8.GetString(buffer);
                Message = Uri.UnescapeDataString(Message);
                Console.WriteLine(",{DateTime.Now:g} Client IP:{TcpClient.Client.RemoteEndPoint}n{Message}");

n{DateTime.Now:g} Klient IP:{TcpClient.Client.RemoteEndPoint}n{Message}");
RequestParse();
HeadersValues = HeaderValues();
FormValues = ContentValues();
UserParse();
ProfileParse();
OnRead?.Invoke(ClientStream, this);
}
}

private void RequestParse()
{
Match methodParse = Regex.Match(Message, @"(^w+)s+([^s?]+)[^s]*s+HTTP/.*|");
RequestMethod = methodParse.Groups[1].Value.Trim();
RequestUrl = methodParse.Groups[2].Value.Trim();
}
private void UserParse()
{
string cookie;
försök
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
försök
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
catch { }
}
}
catch { }

}
private List HeaderValues()
{
var values = new List();
var parse = Regex.Matches(Message, @"(.*?): (.*?)n");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim()
});
}
return values;
}

private void ProfileParse()
{
if (RequestUrl.Contains("@"))
{
RequestProfile = databaseWorker.FindUser(RequestUrl.Substring(2));
RequestUrl = "/profile";
}
}
private List ContentValues()
{
var values = new List();
var output = Message.Trim('n').Split().Last();
var parse = Regex.Matches(output, @"([^&].*?)=([^&]*b)");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim().Replace('+', ' ')
});
}
return values;
}
}
}


KÀrnan handlar om att anvÀnda reguljÀra uttryck för att dela upp en begÀran i delar. Vi fÄr ett meddelande frÄn klienten, identifierar den första raden som innehÄller metoden och URL:en för begÀran. Sedan lÀser vi rubrikerna och samlar dem i en array i formatet Rubriknamn=InnehÄll, och vi letar ocksÄ efter eventuell medföljande information (t.ex. querystring) som vi ocksÄ lÀgger i en liknande array. Dessutom bestÀmmer parsern om den aktuella klienten Àr autentiserad och sparar dess data. Alla begÀrningar frÄn autentiserade klienter innehÄller en autentisering hash som lagras i cookies, vilket gör att vi kan separera den framtida logiken för tvÄ typer av klienter och ge dem rÀtt svar.

Och en liten, trevlig funktion som skulle vara vÀrt att lyfta ut till en separat modul Àr omvandlingen av begÀrningar som "site.com/@UserName" till dynamiskt genererade sidor för anvÀndare. Efter att begÀran har behandlats, trÀder följande moduler in i bilden.

Kapitel 3. Installation av ratten, smörjning av kedjan

SÄ snart parsern har arbetat klart, trÀder en hanterare in som ger ytterligare instruktioner till servern och delar upp kontrollen i tvÄ delar.

En enkel hanterare

using ClearServer.Core.UserController;
using System.Net.Security;
namespace ClearServer.Core.Requester
{
    public class RequestHandler
    {
        public static void OnHandle(SslStream ClientStream, RequestContext context)
        {

            if (context.CurrentUser != null)
            {
                new AuthUserController(ClientStream, context);
            }
            else 
            {
                new NonAuthUserController(ClientStream, context);
            };
        }
    }
}

I grunden finns det bara en kontroll av anvÀndarens autentisering, varefter begÀran börjar bearbetas.

Klientkontroller
Om anvÀndaren inte Àr autentiserad Àr funktionaliteten begrÀnsad till att visa anvÀndarprofiler och registrerings- och inloggningsfönster. Koden för autentiserade anvÀndare ser ungefÀr likadan ut, sÄ jag ser ingen anledning att duplicera den.

Inte autentiserad anvÀndare

using ClearServer.Core.Requester;
using System.IO;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class NonAuthUserController
    {
        private readonly SslStream ClientStream;
        private readonly RequestContext Context;
        private readonly WriteController WriteController;
        private readonly AuthorizationController AuthorizationController;

        private readonly string ViewPath = "C:\/Users\/drdre\/source\/repos\/ClearServer\/View";

        public NonAuthUserController(SslStream clientStream, RequestContext context)
        {
            this.ClientStream = clientStream;
            this.Context = context;
            this.WriteController = new WriteController(clientStream);
            this.AuthorizationController = new AuthorizationController(clientStream, context);
            ResourceLoad();
        }

        void ResourceLoad()
        {
            string[] blockextension = new string[] {"cshtml", "html", "htm"};
            bool block = false;
            foreach (var item in blockextension)
            {
                if (Context.RequestUrl.Contains(item))
                {
                    block = true;
                    break;
                }
            }
            string FilePath = "";
            string Header = "";
            var RazorController = new RazorController(Context, ClientStream);
            
            switch (Context.RequestMethod)
            {
                case "GET":
                    switch (Context.RequestUrl)
                    {
                        case "\/":
                            FilePath = ViewPath + "\/loginForm.html";
                            Header =

quot;HTTP\/1.1 200 OKnContent-Type: text\/html";
WriteController.DefaultWriter(Header, FilePath);
break;
case "\/profile":
RazorController.ProfileLoader(ViewPath);
break;
default:
//ĐČ ĐŽĐ°ĐœĐœĐŸĐŒ Đ±Đ»ĐŸĐșĐ” ĐșĐŸĐŽĐ° ĐżŃ€ĐŸĐžŃŃ…ĐŸĐŽĐžŃ‚ ĐŸŃ‚ŃĐ”Ń‡Đ”ĐœĐžĐ” Đ·Đ°ĐżŃ€ĐŸŃĐŸĐČ Đș сДрĐČĐ”Ń€Ńƒ ĐżĐŸ ĐżŃ€ŃĐŒĐŸĐŒŃƒ Đ°ĐŽŃ€Đ”ŃŃƒ ŃŃ‚Ń€Đ°ĐœĐžŃ†Ń‹ ĐČОЎа site.com/page.html
if (!File.Exists(ViewPath + Context.RequestUrl) | block)
{
RazorController.ErrorLoader(404);

}
else if (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;

case "POST":
AuthorizationController.MethodRecognizer();
break;

}

}

}
}


Och sjÀlvklart ska anvÀndaren fÄ nÄgot innehÄll pÄ sidorna, sÄ för svaren finns följande modul som ansvarar för att svara pÄ resursförfrÄgningar.

WriterController

using System;
using System.IO;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    public class WriteController
    {
        SslStream ClientStream;
        public WriteController(SslStream ClientStream)
        {
            this.ClientStream = ClientStream;
        }

        public void DefaultWriter(string Header, string FilePath)
        {
            FileStream fileStream;
            try
            {
                fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                Header =

quot;{Header}nContent-Length: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = new byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
catch { }
}

public string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP\/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
ÄtervÀnda

quot;{Header} text\/html";
case ".css":
ÄtervÀnda

quot;{Header} text\/css";
case ".js":
ÄtervÀnda

quot;{Header} text\/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
ÄtervÀnda

quot;{Header} image\/{extension}";
default:
if (extension.Length > 1)
{
ÄtervÀnda

quot;{Header} application\" + extension.Substring(1);
}
else
{
ÄtervÀnda

quot;{Header} application\/unknown";
}
}
}

public void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}

public void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}


Men för att visa anvÀndaren sin profil och profilerna för andra anvÀndare bestÀmde jag mig för att anvÀnda RazorEngine, eller snarare en del av den. Den inkluderar ocksÄ hantering av felaktiga förfrÄgningar och ger motsvarande felkod.

RazorController

using ClearServer.Core.Requester;
using RazorEngine;
using RazorEngine.Templating;
using System;
using System.IO;
using System.Net;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class RazorController
    {
        private RequestContext Context;
        private SslStream ClientStream;
        dynamic PageContent;


        public RazorController(RequestContext context, SslStream clientStream)
        {
            this.Context = context;
            this.ClientStream = clientStream;

        }

        public void ProfileLoader(string ViewPath)
        {
            string Filepath = ViewPath + "\/profile.cshtml";
            if (Context.RequestProfile != null)
            {
                if (Context.CurrentUser != null && Context.RequestProfile.login == Context.CurrentUser.login)
                {
                    try
                    {
                        PageContent = new { isAuth = true, Name = Context.CurrentUser.name, Login = Context.CurrentUser.login, Skills = Context.CurrentUser.skills };
                        ClientSend(Filepath, Context.CurrentUser.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }

                }
                else
                {
                    try
                    {
                        PageContent = new { isAuth = false, Name = Context.RequestProfile.name, Login = Context.RequestProfile.login, Skills = Context.RequestProfile.skills };
                        ClientSend(Filepath, "PublicProfile:" + Context.RequestProfile.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }
                }
            }
            else
            {
                ErrorLoader(404);
            }


        }

        public void ErrorLoader(int Code)
        {
            try
            {
                PageContent = new { ErrorCode = Code, Message = ((HttpStatusCode)Code).ToString() };
                string ErrorPage = "C:\Users\drdre\source\repos\ClearServer\View\Errors\ErrorPage.cshtml";
                ClientSend(ErrorPage, Code.ToString());
            }
            catch { }

        }

        private void ClientSend(string FilePath, string Key)
        {
            var template = File.ReadAllText(FilePath);
            var result = Engine.Razor.RunCompile(template, Key, null, (object)PageContent);
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(result);
            ClientStream.BeginWrite(buffer, 0, buffer.Length, OnClientSend, ClientStream);
        }

        private void OnClientSend(IAsyncResult ar)
        {
            if (ar.IsCompleted)
            {
                ClientStream.Close();
            }
        }
    }
}

Naturligtvis, för att autentisering av inloggade anvÀndare ska fungera, behövs autentisering. Autentiseringsmodulen interagerar med databasen. De uppgifter som fÄs frÄn formulÀren pÄ webbplatsen parsas frÄn kontexten, anvÀndaren sparas och fÄr i utbyte cookies och Ätkomst till tjÀnsten.

Autentiseringsmodul

using ClearServer.Core.Cookies;
using ClearServer.Core.Requester;
using ClearServer.Core.Security;
using System;
using System.Linq;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    internal class AuthorizationController
    {
        private SslStream ClientStream;
        private RequestContext Context;
        private UserCookies cookies;
        private WriteController WriteController;
        DatabaseWorker DatabaseWorker;
        RazorController RazorController;
        PasswordHasher PasswordHasher;
        public AuthorizationController(SslStream clientStream, RequestContext context)
        {
            ClientStream = clientStream;
            Context = context;
            DatabaseWorker = new DatabaseWorker();
            WriteController = new WriteController(ClientStream);
            RazorController = new RazorController(context, clientStream);
            PasswordHasher = new PasswordHasher();
        }

        internal void MethodRecognizer()
        {
            if (Context.FormValues.Count == 2 && Context.FormValues.Any(x => x.Name == "password")) Authorize();
            else if (Context.FormValues.Count == 3 && Context.FormValues.Any(x => x.Name == "regPass")) Registration();
            else
            {
                RazorController.ErrorLoader(401);
            }
        }

        private void Authorize()
        {
            var values = Context.FormValues;
            var user = new User()
            {
                login = values[0].Value,
                password = PasswordHasher.PasswordHash(values[1].Value)
            };
            user = DatabaseWorker.UserAuth(user);
            if (user != null)
            {
                cookies = new UserCookies(user.login, user.password);
                user.cookie = cookies.AuthCookie;
                DatabaseWorker.UserUpdate(user);
                var response = Encoding.UTF8.GetBytes(

"HTTP/1.1 301 Moved Permanently Location: /@{user.login} Set-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);

}
else
{
RazorController.ErrorLoader(401);

}
}

private void Registration()
{
var values = Context.FormValues;
var user = new User()
{
name = values[0].Value,
login = values[1].Value,
password = PasswordHasher.PasswordHash(values[2].Value),
};
cookies = new UserCookies(user.login, user.password);
user.cookie = cookies.AuthCookie;
if (DatabaseWorker.LoginValidate(user.login))
{
Console.WriteLine("User ready");
Console.WriteLine(

"{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes(

"HTTP/1.1 301 Moved Permanently Location: /@{user.login} Set-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
else
{
RazorController.ErrorLoader(401);
}
}
}
}


SÄ hÀr ser databashanteringen ut:

Databas

using ClearServer.Core.UserController;
using System;
using System.Data.Linq;
using System.Linq;

namespace ClearServer
{
    class DatabaseWorker
    {

        private readonly Table users = null;
        private readonly DataContext DataBase = null;
        private const string connectionStr = @"vÀgen till databasen";

        public DatabaseWorker()
        {
            DataBase = new DataContext(connectionStr);
            users = DataBase.GetTable();
        }

        public User UserAuth(User User)
        {
            try
            {
                var user = users.SingleOrDefault(t => t.login.ToLower() == User.login.ToLower() && t.password == User.password);
                if (user != null)
                    return user;
                else
                    return null;
            }
            catch (Exception)
            {
                return null;
            }

        }

        public void UserRegister(User user)
        {
            try
            {
                users.InsertOnSubmit(user);
                DataBase.SubmitChanges();
                Console.WriteLine("User {user.name} med id {user.uid} tillagd");
            }

"User {user.name} med id {user.uid} tillagd";
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
catch (Exception e)
{
Console.WriteLine(e);
}

}

public bool LoginValidate(string login)
{
if (users.Any(x => x.login.ToLower() == login.ToLower()))
{
Console.WriteLine("AnvÀndarnamnet finns redan");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(

"AnvÀndare {UserToUpdate.name} med id {UserToUpdate.uid} uppdaterad";
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
public User CookieValidate(string CookieInput)
{
User user = null;
försök
{
user = users.SingleOrDefault(x => x.cookie == CookieInput);
}
catch
{
return null;
}
if (user != null) return user;
else return null;
}
public User FindUser(string login)
{
User user = null;
försök
{
user = users.Single(x => x.login.ToLower() == login.ToLower());
if (user != null)
{
return user;
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}


Och allt fungerar som en klocka, autentisering och registrering fungerar, minimiÄtkomst till tjÀnsten finns redan och nu Àr det dags att skriva applikationen och koppla ihop allt detta med de grundlÀggande funktioner som hela denna process syftar till.

Kapitel 4. Att skapa nÄgot nytt

För att minska arbetsinsatsen för att skriva tvĂ„ applikationer för tvĂ„ plattformar beslutade jag att skapa en korsplattform pĂ„ Xamarin.Forms. Återigen, tack vare att den Ă€r skriven i C#. Efter att ha gjort en testapplikation som helt enkelt skickar data till servern, stötte jag pĂ„ en intressant punkt. För att göra en begĂ€ran frĂ„n enheten implementerade jag av nyfikenhet detta med HttpClient och skickade till servern en HttpRequestMessage som innehöll data frĂ„n inloggningsformulĂ€ret i json-format. Utan att förvĂ€nta mig mycket, öppnade jag serverloggen och sĂ„g dĂ€r en begĂ€ran frĂ„n enheten med all data. En lĂ€tt förvirring, insikten av allt som hade gjorts under de senaste tre veckorna av lĂ„nga kvĂ€llar. För att kontrollera riktigheten av de skickade uppgifterna satte jag upp en testserver med HttpListener. NĂ€r jag fick en ny begĂ€ran dĂ€r, delade jag upp den i delar med bara nĂ„gra rader kod och fick en KeyValuePair med data frĂ„n formulĂ€ret. Återanalysen av begĂ€ran minskade till tvĂ„ rader.

Jag började testa vidare, tidigare nÀmndes inte, men pÄ den gamla servern hade jag ocksÄ implementerat en chatt baserad pÄ webbsockets. Den fungerade ganska bra, men sjÀlva principen för interaktion via Tcp var nedslÄende, det krÀvdes för mycket extra arbete för att pÄ ett korrekt sÀtt bygga interaktionen mellan tvÄ anvÀndare med loggföring av konversationen. Det handlade om att parsa begÀran för att vÀxla anslutningen och samla svar enligt protokollet RFC 6455. DÀrför beslutade jag att skapa en enkel websocket-anslutning pÄ testservern. Bara av nyfikenhet.

Anslutning till chatten

 privat statisk async void HanteraWebsocket(HttpListenerContext context)
        {
            var socketContext = await context.AcceptWebSocketAsync(null);
            var socket = socketContext.WebSocket;
            Locker.EnterWriteLock();
            try
            {
                Clients.Add(socket);
            }
            finally
            {
                Locker.ExitWriteLock();
            }

            while (true)
            {
                var buffer = new ArraySegment(new byte[1024]);
                var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
                var str = Encoding.Default.GetString(buffer);
                Console.WriteLine(str);

                for (int i = 0; i < Clients.Count; i++)
                {
                    WebSocket client = Clients[i];

                    try
                    {
                        if (client.State == WebSocketState.Open)
                        {
                            
                            await client.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    catch (ObjectDisposedException)
                    {
                        Locker.EnterWriteLock();
                        try
                        {
                            Clients.Remove(client);
                            i--;
                        }
                        finally
                        {
                            Locker.ExitWriteLock();
                        }
                    }
                }
            }
        }

Och det fungerade. Servern konfigurerade sjÀlv anslutningen och genererade svarnyckeln. Jag behövde till och med inte stÀlla in serverregistreringen för ssl separat, det var tillrÀckligt med att certifikatet redan var installerat i systemet pÄ rÀtt port.

PÄ enheten och pÄ webbplatsen utbytte tvÄ klienter meddelanden, allt registrerades. Inga stora parserar som saktade ner serverns arbete behövdes. Svarstiden minskade frÄn 200 ms till 40-30 ms. Och jag kom fram till den enda rÀtta lösningen.

Avancerad cykelbyggnad eller klient-serverapplikation baserad pÄ C# .Net framework

Att avbryta den nuvarande implementeringen av servern pÄ Tcp och skriva om allt för Http. Nu befinner sig projektet i en omdesignfas, men enligt helt andra principer för samverkan. Enheternas och webbplatsens arbete Àr synkroniserat och justerat och har en gemensam koncept, med den enda skillnaden att det inte behövs genereras html-sidor för enheterna.

Utdata

«Utan att veta vad som döljer sig, inte gÄ in i vattnet» Jag tror att jag innan jag började arbeta borde ha definierat syften och mÄl tydligare, samt fördjupat mig i studiet av nödvÀndiga teknologier och metoder för deras implementering pÄ olika kunder. Projektet Àr redan nÀra att avslutas, men jag kanske kommer tillbaka för att berÀtta om hur jag Äterigen har gjort misstag med vissa saker. Jag har lÀrt mig mycket under utvecklingsprocessen, men det finns Ànnu mer att lÀra i framtiden. Om du har lÀst fram till detta, tack för det.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster