Advanced na pagbibisikleta o client-server na application batay sa C# .Net framework

Pagpasok

Nagsimula ang lahat nang iminungkahi ng isang kasamahan na gumawa ako ng isang maliit na serbisyo sa web. Ito ay dapat na isang bagay tulad ng Tinder, ngunit para sa IT crowd. Ang pag-andar ay napaka-simple, magrehistro ka, punan ang isang profile at lumipat sa pangunahing punto, lalo na ang paghahanap ng taong makakausap at pagpapalawak ng iyong mga koneksyon at paggawa ng mga bagong kakilala.

Dito kailangan kong mag-retreat at magkuwento ng kaunti tungkol sa aking sarili, upang sa hinaharap ay mas malinaw kung bakit ako gumawa ng mga ganitong hakbang sa pag-unlad.

Sa sandaling hawak ko ang posisyon ng Teknikal na Artist sa isang studio ng laro, ang aking karanasan sa programming sa C# ay binuo lamang sa pagsulat ng mga script at utility para sa Unity at, bilang karagdagan dito, ang paglikha ng mga plugin para sa mababang antas ng trabaho sa mga Android device. Hindi pa ako nakipagsapalaran sa kabila ng maliit na mundong ito, at pagkatapos ay lumitaw ang gayong pagkakataon.

Bahagi 1. Frame prototyping

Nang magpasya kung ano ang magiging serbisyong ito, nagsimula akong maghanap ng mga opsyon para sa pagpapatupad. Ang pinakamadaling bagay ay ang makahanap ng isang uri ng handa na solusyon, kung saan, tulad ng isang kuwago sa isang globo, ang aming mga mekanika ay maaaring mahila at ang lahat ay maaaring malantad sa pampublikong pagsisiyasat.
Ngunit hindi ito kawili-wili, wala akong nakitang anumang hamon o kahulugan dito, at samakatuwid ay nagsimula akong mag-aral ng mga teknolohiya sa web at mga pamamaraan ng pakikipag-ugnayan sa kanila.

Nagsimula akong mag-aral sa pamamagitan ng pagtingin sa mga artikulo at dokumentasyon sa C# .Net. Dito ko nakita ang iba't ibang paraan upang makumpleto ang gawain. Maraming mekanismo para sa pakikipag-ugnayan sa network, mula sa mga ganap na solusyon tulad ng mga serbisyo ng ASP.Net o Azure, hanggang sa direktang pakikipag-ugnayan sa mga koneksyon sa TcpHttp.

Sa aking unang pagtatangka sa ASP, agad kong tinanggihan ito; sa aking palagay, ito ay napakahirap na desisyon para sa aming serbisyo. Hindi namin gagamitin ang kahit isang third ng mga kakayahan ng platform na ito, kaya ipinagpatuloy ko ang aking paghahanap. Ang pagpipilian ay sa pagitan ng TCP at Http client-server. Dito, sa HabrΓ©, nakatagpo ako ng isang artikulo tungkol sa multithreaded server, nang nakolekta at nasubok ito, nagpasya akong partikular na tumuon sa pakikipag-ugnayan sa mga koneksyon sa TCP, sa ilang kadahilanan naisip ko na hindi ako papayagan ng http na lumikha ng isang cross-platform na solusyon.

Kasama sa unang bersyon ng server ang pagpoproseso ng koneksyon, nagsilbi ng static na nilalaman ng web page, at may kasamang database ng user. At para magsimula, nagpasya akong bumuo ng functionality para sa pagtatrabaho sa site, para maidagdag ko sa ibang pagkakataon ang pagpoproseso ng application sa Android at iOS.

Narito ang ilang code
Ang pangunahing thread, tumatanggap ng mga kliyente sa isang walang katapusang loop:

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

Ang tagapangasiwa ng kliyente mismo:

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

At ang unang database na binuo sa lokal na SQL:

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

Tulad ng nakikita mo, ang bersyon na ito ay naiiba nang kaunti mula sa isa sa artikulo. Sa katunayan, dito lang namin idinagdag ang paglo-load ng mga pahina mula sa isang folder sa computer at isang database (na, sa pamamagitan ng paraan, ay hindi gumana sa bersyon na ito dahil sa hindi tamang arkitektura ng koneksyon).

Kabanata 2. Pag-screw ng mga gulong

Matapos subukan ang server, napagpasyahan ko na ito ay isang mahusay na solusyon (spoiler: hindi), para sa aming serbisyo, kaya nagsimulang makakuha ng lohika ang proyekto.
Hakbang-hakbang, nagsimulang lumitaw ang mga bagong module at lumawak ang functionality ng server. Ang server ay nakakuha ng pansubok na domain at pag-encrypt ng koneksyon sa SSL.

Kaunti pang code na naglalarawan sa lohika ng pagpoproseso ng server at kliyente
Isang na-update na bersyon ng server na kinabibilangan ng paggamit ng isang certificate.

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

At isa ring bagong tagapangasiwa ng kliyente na may pahintulot ng 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);
}

}
}

Ngunit dahil eksklusibong tumatakbo ang server sa isang koneksyon sa TCP, kinakailangan na lumikha ng isang module na maaaring makilala ang konteksto ng kahilingan. Napagpasyahan ko na ang isang parser ay magiging angkop dito na maghahati sa kahilingan mula sa kliyente sa magkakahiwalay na bahagi kung saan maaari akong makipag-ugnayan upang mabigyan ang kliyente ng mga kinakailangang sagot.

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

Ang kakanyahan nito ay hatiin ang kahilingan sa mga bahagi gamit ang mga regular na expression. Nakatanggap kami ng mensahe mula sa kliyente, piliin ang unang linya, na naglalaman ng paraan at humiling ng url. Pagkatapos ay binabasa namin ang mga heading, na inilagay namin sa isang array ng form na HeaderName=Content, at nakita rin namin, kung magagamit, ang kasamang nilalaman (halimbawa, querystring) na inilalagay din namin sa isang katulad na array. Bilang karagdagan, malalaman ng parser kung ang kasalukuyang kliyente ay awtorisado at iniimbak ang kanyang data. Ang lahat ng mga kahilingan mula sa mga awtorisadong kliyente ay naglalaman ng isang hash ng awtorisasyon, na nakaimbak sa cookies, salamat dito posible na paghiwalayin ang karagdagang lohika ng pagpapatakbo para sa dalawang uri ng mga kliyente at bigyan sila ng mga tamang sagot.

Well, isang maliit, magandang feature na sulit na ilagay sa isang hiwalay na module, ang conversion ng mga query tulad ng "site.com/@UserName" sa mga dynamic na nabuong user page. Pagkatapos iproseso ang kahilingan, ang mga sumusunod na module ay gaganap.

Kabanata 3. Pag-install ng manibela, pagpapadulas ng kadena

Sa sandaling makumpleto ng parser ang gawain nito, papasok ang handler, na nagbibigay ng karagdagang mga tagubilin sa server at hinahati ang kontrol sa dalawang bahagi.

Simpleng handler

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

Sa katunayan, mayroon lamang isang tseke para sa awtorisasyon ng user, pagkatapos ay magsisimula ang pagproseso ng kahilingan.

Mga controller ng kliyente
Kung ang user ay hindi awtorisado, ang functionality para sa kanya ay nakabatay lamang sa pagpapakita ng mga profile ng user at ang authorization registration window. Ang code para sa isang awtorisadong gumagamit ay mukhang pareho, kaya wala akong nakikitang dahilan upang i-duplicate ito.

Hindi awtorisadong gumagamit

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;

}

}

}
}

At siyempre, ang gumagamit ay dapat makatanggap ng ilang uri ng nilalaman ng pahina, kaya para sa mga tugon mayroong sumusunod na module, na responsable para sa pagtugon sa mga kahilingan sa mapagkukunan.

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

Ngunit upang maipakita sa gumagamit ang kanyang profile at ang mga profile ng iba pang mga gumagamit, nagpasya akong gumamit ng RazorEngine, o sa halip ay bahagi nito. Kasama rin dito ang pagproseso ng mga di-wastong kahilingan at pag-isyu ng naaangkop na code ng error.

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

At siyempre, para gumana ang pag-verify ng mga awtorisadong user, kailangan ang pahintulot. Nakikipag-ugnayan ang module ng awtorisasyon sa database. Ang natanggap na data mula sa mga form sa site ay na-parse mula sa konteksto, ang user ay nai-save at bilang kapalit ay tumatanggap ng cookies at access sa serbisyo.

Module ng awtorisasyon

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

At ito ang hitsura ng pagproseso ng database:

Database

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


At lahat ay gumagana tulad ng orasan, awtorisasyon at pagpaparehistro, ang pinakamababang pag-andar para sa pag-access sa serbisyo ay naroroon na at dumating na ang oras upang magsulat ng isang aplikasyon at itali ang buong bagay kasama ang mga pangunahing pag-andar kung saan ang lahat ay ginagawa.

Kabanata 4. Pagtatapon ng bike

Upang mabawasan ang mga gastos sa paggawa sa pagsulat ng dalawang aplikasyon para sa dalawang platform, nagpasya akong gumawa ng cross-platform sa Xamarin.Forms. Muli, salamat sa katotohanan na ito ay nasa C#. Ang pagkakaroon ng isang pagsubok na application na nagpapadala lamang ng data sa server, nakatagpo ako ng isang kawili-wiling punto. Para sa isang kahilingan mula sa isang device, para masaya, ipinatupad ko ito sa HttpClient at ipinadala ito sa server ng HttpRequestMessage, na naglalaman ng data mula sa form ng pahintulot sa format na json. Nang walang partikular na inaasahan, binuksan ko ang log ng server at nakita ko doon ang isang kahilingan mula sa device kasama ang lahat ng data. Isang bahagyang pagkahilo, kaalaman sa lahat ng nagawa sa nakalipas na 3 linggo sa isang malamlam na gabi. Upang suriin ang katumpakan ng ipinadalang data, nag-assemble ako ng isang test server sa HttpListner. Dahil nakatanggap na ako ng isa pang kahilingan, pinaghiwalay ko ito sa ilang linya ng code at nakatanggap ng KeyValuePair ng data mula sa form. Ang pag-parse ng query ay binawasan sa dalawang linya.

Sinimulan ko pa ang pagsubok, hindi ito nabanggit kanina, ngunit sa nakaraang server ay nagpatupad din ako ng chat na binuo sa mga websocket. Ito ay gumana nang maayos, ngunit ang mismong prinsipyo ng pakikipag-ugnayan sa pamamagitan ng Tcp ay nakapanlulumo; masyadong maraming hindi kinakailangang gawain ang kailangang gawin upang mahusay na mabuo ang pakikipag-ugnayan ng dalawang user na may isang talaan ng sulat. Kabilang dito ang pag-parse ng isang kahilingan upang ilipat ang koneksyon at pagkolekta ng tugon gamit ang RFC 6455 protocol. Samakatuwid, sa pagsubok na server, nagpasya akong lumikha ng isang simpleng koneksyon sa websocket. Katuwaan lang.

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

At ito ay gumana. Ang server mismo ang nag-configure ng koneksyon at nakabuo ng response key. Hindi ko na kailangang hiwalay na i-configure ang pagpaparehistro ng server sa pamamagitan ng SSL; sapat na na ang system ay mayroon nang naka-install na sertipiko sa kinakailangang port.

Sa gilid ng device at sa gilid ng site, dalawang kliyente ang nagpalitan ng mensahe, lahat ng ito ay naka-log. Walang malalaking parser na nagpapabagal sa server, wala sa mga ito ang kinakailangan. Bumaba ang oras ng pagtugon mula 200ms hanggang 40-30ms. At dumating ako sa tanging tamang desisyon.

Advanced na pagbibisikleta o client-server na application batay sa C# .Net framework

Itapon ang kasalukuyang pagpapatupad ng server sa Tcp at muling isulat ang lahat sa ilalim ng Http. Ngayon ang proyekto ay nasa yugto ng muling pagdidisenyo, ngunit ayon sa ganap na magkakaibang mga prinsipyo ng pakikipag-ugnayan. Ang pagpapatakbo ng mga device at ang site ay naka-synchronize at nag-debug at may karaniwang konsepto, na ang pagkakaiba lang ay hindi na kailangang bumuo ng mga HTML na page para sa mga device.

Pagbubuhos

"Kung hindi mo alam ang tawid, huwag pumunta sa tubig" Sa palagay ko, bago simulan ang trabaho, dapat akong magkaroon ng mas malinaw na tinukoy na mga layunin at layunin, pati na rin ang pag-aaral ng mga kinakailangang teknolohiya at pamamaraan para sa kanilang pagpapatupad sa iba't ibang mga kliyente. Ang proyekto ay malapit nang makumpleto, ngunit marahil ay babalik ako upang pag-usapan kung paano ko na-save muli ang ilang mga bagay. Marami akong natutunan sa proseso ng pag-unlad, ngunit marami pa akong dapat matutunan sa hinaharap. Kung nabasa mo na ito, salamat sa paggawa nito.

Pinagmulan: www.habr.com