Ndërtimi i avancuar i biçikletave ose aplikacioni klient-server i bazuar në kornizën C# .Net

Hyrje

Gjithçka filloi kur një koleg më sugjeroi të krijoj një shërbim të vogël në internet. Supozohej të ishte diçka si Tinder, por për turmën e IT. Funksionaliteti është jashtëzakonisht i thjeshtë, ju regjistroheni, plotësoni një profil dhe kaloni te pika kryesore, përkatësisht gjetja e një personi për të biseduar dhe zgjerimi i lidhjeve tuaja dhe krijimi i njohjeve të reja.

Këtu më duhet të bëj një tërheqje dhe të tregoj pak për veten time, në mënyrë që në të ardhmen të jetë më e qartë pse kam ndërmarrë hapa të tillë në zhvillim.

Për momentin që mbaj pozicionin e Artistit Teknik në një studio lojërash, përvoja ime e programimit në C# u ndërtua vetëm në shkrimin e skripteve dhe shërbimeve për Unity dhe, përveç kësaj, në krijimin e shtojcave për punë të nivelit të ulët me pajisjet Android. Nuk kisha dalë ende përtej kësaj bote të vogël dhe më pas lindi një mundësi e tillë.

Pjesa 1. Prototipi i kornizës

Pasi vendosa se si do të ishte ky shërbim, fillova të kërkoja opsione për zbatim. Gjëja më e lehtë do të ishte gjetja e një lloj zgjidhjeje të gatshme, mbi të cilën, si një buf në një rruzull, mekanika jonë mund të tërhiqet dhe e gjithë kjo të ekspozohet ndaj censurës publike.
Por kjo nuk është interesante, unë nuk pashë ndonjë sfidë apo kuptim në të, dhe për këtë arsye fillova të studioj teknologjitë e uebit dhe metodat e bashkëveprimit me to.

Fillova të studioja duke parë artikuj dhe dokumentacion në C# .Net. Këtu gjeta mënyra të ndryshme për të përfunduar detyrën. Ka shumë mekanizma për të bashkëvepruar me rrjetin, nga zgjidhjet e plota si shërbimet ASP.Net ose Azure, deri te ndërveprimi i drejtpërdrejtë me lidhjet TcpHttp.

Pasi bëra përpjekjen time të parë me ASP-në, menjëherë e refuzova; për mendimin tim, ky ishte një vendim shumë i vështirë për shërbimin tonë. Ne nuk do të përdornim as një të tretën e aftësive të kësaj platforme, ndaj vazhdova kërkimin tim. Zgjedhja ishte midis TCP dhe Http klient-server. Këtu, në Habré, hasa në një artikull rreth server me shumë fije, pasi e mblodha dhe e testova atë, vendosa të fokusohem posaçërisht në ndërveprimin me lidhjet TCP, për disa arsye mendova se http nuk do të më lejonte të krijoja një zgjidhje ndër-platformë.

Versioni i parë i serverit përfshinte përpunimin e lidhjes, shërbente përmbajtje statike të faqes së internetit dhe përfshinte një bazë të dhënash të përdoruesit. Dhe për të filluar, vendosa të ndërtoj funksionalitet për të punuar me sitin, në mënyrë që më vonë të mund të shtoja përpunimin e aplikacionit në Android dhe iOS.

Këtu është një kod
Fijet kryesore që pranojnë klientët në një lak të pafund:

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);
        }
    }
}

Vetë mbajtësi i klientit:

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer
{
    class Client
    {


        public Client(TcpClient Client)
        {

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

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

            Match ReqMatch = Regex.Match(Message, @"^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 FilePath =

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

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

string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));

string ContentType = "";

switch (Extension)
{
case ".htm":
case ".html":
ContentType = "text/html";
break;
case ".css":
ContentType = "text/css";
break;
case ".js":
ContentType = "text/javascript";
break;
case ".jpg":
ContentType = "image/jpeg";
break;
case ".jpeg":
case ".png":
case ".gif":
ContentType =


quot;image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType =


quot;application/{Extension.Substring(1)}";
}
else
{
ContentType = "application/unknown";
}
break;
}

FileStream FS;
try
{
FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
ErrorWorker.SendError(Client, 500);
return;
}

string Headers =


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

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

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

Dhe baza e parë e të dhënave e ndërtuar në SQL lokale:

using System;
using 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<User> users = db.GetTable<User>();
                foreach (var item in users)
                {
                    Console.WriteLine(

quot;{item.login} {item.password}");
}
}
}
}
}

Siç mund ta shihni, ky version ndryshon pak nga ai në artikull. Në fakt, këtu thjesht shtuam ngarkimin e faqeve nga një dosje në kompjuter dhe një bazë të dhënash (e cila, nga rruga, nuk funksionoi në këtë version për shkak të arkitekturës së gabuar të lidhjes).

Kapitulli 2. Vidhosja e rrotave

Pas testimit të serverit, arrita në përfundimin se kjo do të ishte një zgjidhje e shkëlqyer (spoiler: jo), për shërbimin tonë, kështu që projekti filloi të marrë logjikë.
Hap pas hapi, modulet e reja filluan të shfaqen dhe funksionaliteti i serverit u zgjerua. Serveri ka fituar një domen provë dhe kriptim të lidhjes SSL.

Pak më shumë kod që përshkruan logjikën e përpunimit të serverit dhe klientit
Një version i përditësuar i serverit që përfshin përdorimin e një certifikate.

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();
        }
    }
}

Dhe gjithashtu një mbajtës i ri klienti me autorizim SSL:

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" +


quot;|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Authentication failed - closing the connection.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}

}
}

Por meqenëse serveri funksionon ekskluzivisht në një lidhje TCP, është e nevojshme të krijohet një modul që mund të njohë kontekstin e kërkesës. Vendosa që këtu do të ishte i përshtatshëm një analizues që do ta thyente kërkesën nga klienti në pjesë të veçanta me të cilat mund të ndërveproja për t'i dhënë klientit përgjigjet e nevojshme.

analizues

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<RequestValues> HeadersValues;
        public List<RequestValues> FormValues;
        private TcpClient TcpClient;

        private event Action<SslStream, RequestContext> 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(

quot;n{DateTime.Now:g} Client 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;
try
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
try
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
catch { }
}
}
catch { }

}
private List<RequestValues> HeaderValues()
{
var values = new List<RequestValues>();
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<RequestValues> ContentValues()
{
var values = new List<RequestValues>();
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;
}
}
}

Thelbi i saj është të ndajë kërkesën në pjesë duke përdorur shprehje të rregullta. Marrim një mesazh nga klienti, zgjedhim rreshtin e parë, i cili përmban metodën dhe url-në e kërkesës. Më pas lexojmë titujt, të cilët i vendosim në një grup të formës HeaderName=Content, dhe gjejmë gjithashtu, nëse disponohet, përmbajtje shoqëruese (për shembull, querystring) të cilën e vendosëm gjithashtu në një grup të ngjashëm. Përveç kësaj, analizuesi zbulon nëse klienti aktual është i autorizuar dhe ruan të dhënat e tij. Të gjitha kërkesat nga klientët e autorizuar përmbajnë një hash autorizimi, i cili ruhet në cookie, falë kësaj është e mundur të ndahet logjika e mëtejshme e funksionimit për dy llojet e klientëve dhe t'u jepet përgjigjet e sakta.

Epo, një veçori e vogël, e këndshme që do të ia vlente të vendosej në një modul të veçantë, konvertimi i pyetjeve si "site.com/@UserName" në faqet e përdoruesve të gjeneruara në mënyrë dinamike. Pas përpunimit të kërkesës, modulet e mëposhtme hyjnë në lojë.

Kapitulli 3. Instalimi i timonit, lubrifikimi i zinxhirit

Sapo analizuesi të ketë përfunduar punën e tij, trajtuesi hyn në lojë, duke i dhënë udhëzime të mëtejshme serverit dhe duke e ndarë kontrollin në dy pjesë.

Trajtues i thjeshtë

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);
            };
        }
    }
}

Në fakt, ekziston vetëm një kontroll për autorizimin e përdoruesit, pas së cilës fillon përpunimi i kërkesës.

Kontrollorët e klientëve
Nëse përdoruesi nuk është i autorizuar, atëherë funksionaliteti për të bazohet vetëm në shfaqjen e profileve të përdoruesve dhe dritaren e regjistrimit të autorizimit. Kodi për një përdorues të autorizuar duket pothuajse i njëjtë, kështu që nuk shoh asnjë arsye për ta kopjuar atë.

Përdorues i paautorizuar

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;

}

}

}
}

Dhe sigurisht, përdoruesi duhet të marrë një lloj përmbajtjeje të faqes, kështu që për përgjigjet ekziston moduli i mëposhtëm, i cili është përgjegjës për t'iu përgjigjur kërkesave për burime.

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":
return


quot;{Header} text/html";
case ".css":
return


quot;{Header} text/css";
case ".js":
return


quot;{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
return


quot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
return


quot;{Header} application/" + extension.Substring(1);
}
else
{
return


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();
}
}
}

Por për t'i treguar përdoruesit profilin e tij dhe profilet e përdoruesve të tjerë, vendosa të përdor RazorEngine, ose më saktë një pjesë të tij. Ai gjithashtu përfshin përpunimin e kërkesave të pavlefshme dhe lëshimin e një kodi të duhur gabimi.

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();
            }
        }
    }
}

Dhe sigurisht, që verifikimi i përdoruesve të autorizuar të funksionojë, duhet autorizim. Moduli i autorizimit ndërvepron me bazën e të dhënave. Të dhënat e marra nga formularët në sajt analizohen nga konteksti, përdoruesi ruhet dhe në këmbim merr cookie dhe akses në shërbim.

Moduli i autorizimit

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(

quot;HTTP/1.1 301 Moved PermanentlynLocation: /@{user.login}nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
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(


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


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

Dhe kjo është se si duket përpunimi i bazës së të dhënave:

bazës së të dhënave

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

namespace ClearServer
{
    class DatabaseWorker
    {

        private readonly Table<User> users = null;
        private readonly DataContext DataBase = null;
        private const string connectionStr = @"путькбазе";

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

        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(

quot;User{user.name} with id {user.uid} added");
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("Login already exists");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(


quot;User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
public User CookieValidate(string CookieInput)
{
User user = null;
try
{
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;
try
{
user = users.Single(x => x.login.ToLower() == login.ToLower());
if (user != null)
{
return user;
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}


Dhe gjithçka funksionon si ora, puna e autorizimit dhe e regjistrimit, funksionaliteti minimal për të hyrë në shërbim është tashmë atje dhe ka ardhur koha për të shkruar një aplikacion dhe për të lidhur të gjithë me funksionet kryesore për të cilat po bëhet gjithçka.

Kapitulli 4. Hedhja e biçikletës

Për të ulur kostot e punës për të shkruar dy aplikacione për dy platforma, vendosa të bëj një ndër-platformë në Xamarin.Forms. Përsëri, falë faktit që është në C#. Pasi bëra një aplikacion provë që thjesht dërgon të dhëna në server, hasa në një pikë interesante. Për një kërkesë nga një pajisje, për argëtim, e zbatova në HttpClient dhe e dërgova në serverin HttpRequestMessage, i cili përmban të dhëna nga formulari i autorizimit në formatin json. Pa pritur asgjë veçanërisht, hapa regjistrin e serverit dhe pashë atje një kërkesë nga pajisja me të gjitha të dhënat. Një hutim i lehtë, ndërgjegjësim për gjithçka që është bërë gjatë 3 javëve të fundit në një mbrëmje të lodhur. Për të kontrolluar saktësinë e të dhënave të dërguara, mblodha një server testimi në HttpListner. Pasi mora një kërkesë tjetër tashmë në të, e ndava atë në disa rreshta kodi dhe mora një KeyValuePair të dhënash nga formulari. Analizimi i pyetjes u reduktua në dy rreshta.

Fillova të testoja më tej, nuk u përmend më herët, por në serverin e mëparshëm zbatova gjithashtu një chat të ndërtuar në uebsocket. Ajo funksionoi mjaft mirë, por vetë parimi i ndërveprimit përmes Tcp ishte dëshpërues; duhej bërë shumë punë e panevojshme për të ndërtuar me kompetencë ndërveprimin e dy përdoruesve me një regjistër korrespondence. Kjo përfshin analizimin e një kërkese për të ndërruar lidhjen dhe mbledhjen e një përgjigjeje duke përdorur protokollin RFC 6455. Prandaj, në serverin e testimit, vendosa të krijoj një lidhje të thjeshtë të folesë në internet. Vetëm për argëtim.

Lidhu me bisedën

 private static async void HandleWebsocket(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<byte>(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();
                        }
                    }
                }
            }
        }

Dhe funksionoi. Vetë serveri konfiguroi lidhjen dhe gjeneroi një çelës përgjigjeje. Nuk më duhej as të konfiguroja veçmas regjistrimin e serverit përmes SSL; mjaftonte që sistemi të kishte tashmë një certifikatë të instaluar në portën e kërkuar.

Në anën e pajisjes dhe në anën e faqes, dy klientë shkëmbyen mesazhe, e gjithë kjo u regjistrua. Asnjë analizues i madh që ngadalëson serverin, asnjë nga këto nuk kërkohej. Koha e përgjigjes ka rënë nga 200ms në 40-30ms. Dhe arrita te vendimi i vetëm i duhur.

Ndërtimi i avancuar i biçikletave ose aplikacioni klient-server i bazuar në kornizën C# .Net

Hidhni jashtë implementimin aktual të serverit në Tcp dhe rishkruani gjithçka nën Http. Tani projekti është në fazën e ridizajnimit, por sipas parimeve krejtësisht të ndryshme të ndërveprimit. Funksionimi i pajisjeve dhe faqes është i sinkronizuar dhe debuguar dhe ka një koncept të përbashkët, me ndryshimin e vetëm që nuk ka nevojë të gjenerohen faqe HTML për pajisjet.

Prodhim

"Nëse nuk e njeh Ford, mos u fut në ujë" Mendoj se përpara se të filloj punën, duhet të kem qëllime dhe objektiva më të përcaktuara qartë, si dhe të thellohem në studimin e teknologjive dhe metodave të nevojshme për zbatimin e tyre te klientë të ndryshëm. Projekti tashmë është afër përfundimit, por mbase do të kthehem për të folur për mënyrën se si i ruajta disa gjëra përsëri. Mësova shumë gjatë procesit të zhvillimit, por ka edhe më shumë për të mësuar në të ardhmen. Nëse keni lexuar deri këtu, ju faleminderit që e keni bërë këtë.

Burimi: www.habr.com