Prodhimi i avancuar i bicikletave apo aplikacioni klient-server mbi bazën e C# .Net framework

Hyrje

E gjitha filloi kur një koleg më propozoi të krijoja një shërbim të vogël web. Kjo duhej të bëhej diçka si Tinder, por për komunitetin IT. Funksionaliteti është tejet i thjeshtë: regjistrohesh, plotëson profilin dhe kalon në thelbin e gjërave, dmth të kërkosh bashkëbisedues dhe të zgjerosh lidhjet e tua, duke bërë njohje të reja.

Këtu duhet të bëj një ndalesë dhe të flas pak rreth vetes, që më pas të kuptohet më mirë pse po ndërmerrja pikërisht këto hapa në zhvillim.

Aktualisht unë zë vendin e Artistit Teknik në një studio lojerash, përvoja ime në programim me C# është ndërtuar vetëm mbi krijimin e skripteve dhe utiliteteve për Unity, dhe përveç kësaj, krijimi i plug-ineve për punë me nivele të ulëta me pajisjet Android. Nuk kam dalë akoma përtej këtij rrethi dhe këtu u paraqit një mundësi e tillë.

Pjesa 1. Prototipimi i kornizës

Pas vendosjes së asaj që do të përfaqësonte ky shërbim, fillova të kërkoj mundësi për realizimin e tij. Më e lehta do të ishte të gjeja një zgjidhje të gatshme, në të cilën, si një shtëpi e vogël, mund të shtriheshin mekanizmat tanë dhe ta ekspozonim këtë në dënim publik.
Por këtë arsye, unë fillova të studioj teknologjitë e webit dhe metodat e ndërveprimit me to.

Studimin e fillova duke shikuar artikuj dhe dokumentacion mbi C# .Net. Këtu gjeta mënyra të ndryshme për të realizuar detyrën. Ekzistojnë shumë mekanizma ndërveprimi me rrjetin, nga zgjidhje të plota si ASP.Net dhe shërbimet Azure, deri te ndërveprimi i drejtpërdrejtë me lidhjet TcpHttp.

Pas përpjekjes së parë me ASP, unë e hodha atë menjëherë, sepse mendoj se ishte një zgjidhje shumë e rëndë për shërbimin tonë. As që do ta shfrytëzojmë një e treta e mundësive të kësaj platforme, prandaj vazhdova kërkimin. Zgjedhja ishte midis TCP dhe klient-serverit Http. Aty, në Habrë, kam gjetur një artikull mbi serverin me shumë fije, duke e ndërtuar dhe testuar të cilin, vendosa të fokusohesha pikërisht në ndërveprimin me lidhjet TCP, për arsyen se mendova se http nuk do të më lejonte të krijoja një zgjidhje ndër-platformë.

Versioni e parë e serverit përfshinte përpunimin e lidhjeve, ofronte përmbajtje statike të faqeve të internetit dhe përfshinte një bazë të dhënash përdoruesish. Dhe për të filluar, vendosa të ndërtoj funksionalitetin për të punuar me sitin, që më vonë të mund të integroj këtu edhe përpunimin e aplikacionit në Android dhe iOS.

Këtu pak kod
Gjatë kryesore, në një cikël të pafund duke pritur klientët:

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

Përpunuesi i klientëve:

duke System;
duke System.IO;
duke System.Net.Sockets;
duke System.Text;
duke 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 =

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

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

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

FileStream FS;
provo
{
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 të dhënave e parë e ndërtuar në SQL lokal:
përdorim System;
përdorim 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}"
}
}
}
}
}


Siç mund të vërehet, kjo version është paksa e ngjashme me atë që ishte në artikull. Në thelb, këtu ka vetëm shtimin e ngarkesës së faqeve nga dosja në kompjuter dhe një bazë të dhënash (e cila, për fat të keq, në këtë version nuk funksionoi për shkak të arkitekturës së gabuar të lidhjes).

Kapitulli 2. Shtimi i rrotave

Pas testimit të funksionimit të serverit, arrita në përfundimin se do të ishte një zgjidhje e shkëlqyer (shspoiler: jo), për shërbimin tonë, prandaj projekti filloi të merrte logjikë.
Hap pas hapi, filluan të shfaqeshin module të reja dhe funksionaliteti i serverit u zgjerua. Serveri mori një domen testues dhe ssl kriptimin e lidhjes.

Pak më shumë kod, që përshkruan logjikën e funksionimit të serverit dhe përpunimin e klientëve
Versioni i përditësuar i serverit, duke përfshirë përdorimin e certifikatës.

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


emri 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("Duke i serverit.." + 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("Duke një domen tjetër");
                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 po ashtu një trajtues të ri të klientit me autorizim sipas SSL:

duke ClearServer.Core.Requester;
duke Sistemit;
duke System.Net.Security;
duke System.Net.Sockets;

namespace ClearServer
{
    publike class Klienti
    {
        publike Klienti(TcpClient Klienti)
        {
            SslStream SSlClientStream = new SslStream(Klienti.GetStream(), false);
            provoni
            {
                SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true);
            }
            kapur (Përjashtim e)
            {
                Console.WriteLine(
                    "---------------------------------------------------------------------n" +

"|{DateTime.Now:g}n|------------n|{Klienti.Client.RemoteEndPoint}n|------------n|Përjashtim: {e.Message}n|------------n|Autentikimi dështoi - duke mbyllur lidhjen.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Klienti);
}

}
}


Por, duke qenë se serveri funksionon ekskluzivisht mbi lidhje TCP, është e nevojshme të krijohet një modul që mund të njohë kontekstin e kërkesës. Vendosa se këtu do të përfitoja nga një parser që do të ndante kërkesën nga klienti në pjesë të veçanta, me të cilat mund të interaktoj, në mënyrë që t'i jap klientit përgjigjet e nevojshme.

Parser

duke ClearServer.Core.UserController;
duke ReServer.Core.Classes;
duke System;
duke System.Collections.Generic;
duke System.Linq;
duke System.Net.Security;
duke System.Net.Sockets;
duke System.Text;
duke 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} 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;
provo
{
nëse (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
provo
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
kapërce { }
}
}
kapërce { }

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

private void ProfileParse()
{
nëse (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)");
për secilën (Match match në parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim().Replace('+', ' ')
});
}
kthe values;
}
}
}


Thelbi i tij qëndron në ndarjen e kërkesës në pjesë duke përdorur shprehje të rregullta. Merr një mesazh nga klienti, thekson rreshtin e parë, ku përmban metodën dhe URL-në e kërkesës. Pastaj lexon titujt, të cilët i vendos në një array në formatin EmriTitullit=Përmbajtje, dhe gjithashtu gjen, nëse ka, përmbajtjen shoqëruese (për shembull, querystring) që gjithashtu e vendos në një array të ngjashëm. Për më tepër, parseri përcakton 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ë cookies, duke i mundësuar kështu ndarjen e logjikës së mëtejshme të punës për dy lloje të klientëve dhe ofrimin e përgjigjeve të duhura.

Një veçori e vogël dhe e këndshme që do të ishte mirë të veçohej në një modul të veçantë është transformimi i kërkesave si "site.com/@UserName" në faqe të gjeneruara dinamikisht për përdoruesit. Pas përpunimit të kërkesës, hyjnë në veprim modulet e mëposhtme.

Kreu 3. Instalimi i timonit, lubrifikimi i zinxhirit

Pasi parseri të kryejë punën e tij, ndihmësit hyjnë në veprim, duke dhënë udhëzime të mëtejshme për serverin dhe duke ndarë menaxhimin në dy pjesë.

Ndihmës 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ë thelb, këtu ekziston vetëm një kontroll për autorizimin e përdoruesit, pas të cilit fillon përpunimi i kërkesës.

Kontrolluesit e Klientëve
Nëse përdoruesi nuk është autorizuar, funksionaliteti i tij bazohet vetëm në shfaqjen e profileve të përdoruesve dhe dritares së regjistrimit/autorizimit. Kodi për përdoruesit e autorizuar duket afërsisht i njëjtë, prandaj nuk shoh ndonjë kuptim për ta duplicuar.

Përdoruesi i Paautorizuar

duke ClearServer.Core.Requester;
duke System.IO;
duke 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);

}
ndryshe, nëse (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;

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

}

}

}
}


Natyrisht, përdoruesi duhet të marrë ndonjë përmbajtje të faqeve, kështu që për përgjigjet ekziston moduli në vijim, i cili merret me përgjigjen ndaj kërkesave për burime.

WriterController

duke System;
duke System.IO;
duke System.Net.Security;
duke 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 =

"{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);
}
kapërce { }
}

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

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

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

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

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

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

"{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ë treguar përdoruesit profilin e tij dhe profilin e përdoruesve të tjerë, vendosa të përdor RazorEngine, më saktësisht një pjesë të tij. Ai gjithashtu përfshin trajtimin e kërkesave të papërshtatshme dhe ofrimin e kodit të përshtatshëm të gabimit.

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

Sigurisht, për që të funksionojë kontrolli i përdoruesve të autorizuar, nevojitet autorizimi. Moduli i autorizimit ndërvepron me bazën e të dhënave. Të dhënat e marra nga formularët në faqe përpunohen nga konteksti, përdoruesi ruhet dhe merr në këmbim cookies 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);
}
}
}
}


Kështu duket përpunimi i bazës së të dhënave:

Baza e të dhënave

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 = @"путькбазе";

        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} with id {user.uid} added");
            }

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(

Console.WriteLine("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;
provo
{
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;
provo
{
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 orë, autorizimi dhe regjistrimi janë në funksion, funksionaliteti minimal i aksesit në shërbim është tashmë i pranishëm dhe ka ardhur koha për të shkruar aplikacionin dhe për të lidhur gjithçka me funksionet kryesore, për të cilat gjithçka bëhet.

Kapitulli 4. Heqja e biçikletës

Për të reduktuar kohën e punës për zhvillimin e dy aplikacioneve për dy platforma, vendosa të krijoj një aplikacion multidimensional në Xamarin.Forms. Po ashtu, kjo ishte e mundur sepse është në C#. Pas krijimit të një aplikacioni testues, i cili thjesht dërgon të dhëna në server, u përballa me një moment interesant. Për kërkesën nga pajisja, për të bërë eksperimente, e implementova atë me HttpClient dhe dërgova në server një HttpRequestMessage që përmbante të dhënat nga forma e identifikimit në formatin json. Duke mos pritur ndonjë gjë të veçantë, hapa logun e serverit dhe pashë një kërkesë nga pajisja me të gjitha të dhënat. Një ndalesë e vogël, një vetëdije e gjithçkaje që ishte bërë gjatë tri javëve të fundit në mbrëmjet e mundimshme. Për të verifikuar saktësinë e të dhënave të dërguara, krijova një server testues me HttpListner. Pas marrjes së një kërkese tjetër atje, me disa rreshta kodi e ndava atë në pjesë, duke marrë një KeyValuePair të të dhënave nga forma. Analiza e kërkesës u reduktua në dy rreshta.

Fillova testimin më tej, nuk është përmendur më parë, por në serverin e mëparshëm kam realizuar një chat të ndërtuar mbi websockets. Ai ka funksionuar mjaft mirë, por vetë parimi i ndërveprimit përmes Tcp ishte dekurajues, duke prodhuar shumë gjëra të panevojshme për të ndërtuar saktësisht ndërveprimin midis dy përdoruesve me mbajtjen e një regjistri të bisedave. Kjo përfshinte edhe analizimin e kërkesës për të kaluar lidhjen dhe grumbullimin e përgjigjes sipas protokollit RFC 6455. Prandaj, në serverin tim të testimit, vendosa të krijoja një lidhje të thjeshtë websockets. Pas çdo gjëje, për shkak të interesit.

Koneksioni në chat

 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(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 ajo funksionoi. Serveri e konfiguronte vetë lidhjen, gjeneronte çelësin e përgjigjes. Nuk ishte e nevojshme që të konfiguroja veçmas regjistrimin e serverit për SSL, mjaftonte që në sistem të ishte instaluar certifikata në portin e duhur.

Në anën e pajisjes dhe në anën e sitit dy klientë të exchange mesazheve, gjithë kjo u regjistrua. Nuk ishin të nevojshme parsera të mëdhenj që ngadalësonin punën e serverit, asgjë e tillë nuk u kërkua. Koha e përgjigjes u reduktua nga 200ms në 40-30ms. Dhe arrita në zgjidhjen e vetme të saktë.

Prodhimi i avancuar i bicikletave apo aplikacioni klient-server mbi bazën e C# .Net framework

Të heq aktualen implementim të serverit në Tcp dhe ta shkruaj gjithçka nën Http. Tani projekti është në fazën e riprojektimit, por tashmë sipas parimeve krejt të tjera të bashkëpunimit. Puna e pajisjeve dhe e sitit është sinkronizuar dhe e rregulluar dhe ka një koncept të përbashkët, me diferencën se për pajisjet nuk është e nevojshme të gjenerohen faqe html.

Përfundim

«Mos u fut në ujë pa e ditur se ku është thellësia» Mendoj se para të filloja punët, do të kishte qenë më e qartë të përcaktoi qëllimet dhe detyrat, si dhe të thellohej në studimin e teknologjive dhe metodave të nevojshme për zbatimin e tyre në klientë të ndryshëm. Projekti është gati përfunduar, por ndoshta do të kthehem përsëri për të treguar se si përsëri bëra ndonjë gabim. Kam mësuar shumë gjatë procesit të zhvillimit, por më tej kam shumë për të mësuar. Nëse keni arritur deri në këtë pikë, faleminderit për këtë.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster