Jengo la kina la baisikeli au programu ya seva ya mteja kulingana na mfumo wa C# .Net

Entry

Yote ilianza wakati mwenzangu alipendekeza kwamba nitengeneze huduma ndogo ya wavuti. Ilipaswa kuwa kitu kama Tinder, lakini kwa umati wa IT. Utendaji ni rahisi sana, unajiandikisha, ujaze wasifu na uende kwa jambo kuu, ambalo ni kutafuta mtu wa kuzungumza naye na kupanua miunganisho yako na kufanya marafiki wapya.

Hapa lazima nirudi nyuma na niambie kidogo juu yangu, ili katika siku zijazo iwe wazi zaidi kwa nini nilichukua hatua kama hizo katika maendeleo.

Kwa sasa ninashikilia nafasi ya Msanii wa Kiufundi katika studio moja ya mchezo, uzoefu wangu wa programu katika C# ulijengwa tu kwa kuandika hati na huduma za Umoja na, pamoja na hili, kuunda programu-jalizi za kazi ya kiwango cha chini na vifaa vya Android. Nilikuwa bado sijajitosa zaidi ya ulimwengu huu mdogo, na ndipo fursa kama hiyo ikatokea.

Sehemu ya 1: Uwekaji mfano wa fremu

Baada ya kuamua huduma hii itakuwaje, nilianza kutafuta chaguzi za utekelezaji. Jambo rahisi zaidi itakuwa kupata aina fulani ya suluhisho iliyotengenezwa tayari, ambayo, kama bundi kwenye ulimwengu, mechanics yetu inaweza kuvutwa na jambo zima linaweza kuonyeshwa kwa lawama za umma.
Lakini hii haipendezi, sikuona changamoto yoyote au hisia ndani yake, na kwa hivyo nilianza kusoma teknolojia za wavuti na njia za kuingiliana nao.

Nilianza kusoma kwa kuangalia makala na nyaraka kwenye C# .Net. Hapa nilipata njia mbalimbali za kukamilisha kazi. Kuna njia nyingi za kuingiliana na mtandao, kutoka kwa suluhisho kamili kama ASP.Net au huduma za Azure, ili kuelekeza mwingiliano na miunganisho ya TcpHttp.

Baada ya kufanya jaribio langu la kwanza na ASP, niliikataa mara moja; kwa maoni yangu, huu ulikuwa uamuzi mgumu sana kwa huduma yetu. Hatungetumia hata theluthi moja ya uwezo wa jukwaa hili, kwa hivyo niliendelea na utafutaji wangu. Chaguo lilikuwa kati ya TCP na seva ya mteja ya Http. Hapa, kwa Habre, nilipata makala kuhusu seva yenye nyuzi nyingi, baada ya kuikusanya na kuijaribu, niliamua kuzingatia hasa mwingiliano na viunganisho vya TCP, kwa sababu fulani nilifikiri kwamba http haitaniruhusu kuunda suluhisho la jukwaa la msalaba.

Toleo la kwanza la seva lilijumuisha uchakataji wa muunganisho, lilitoa maudhui tuli ya ukurasa wa wavuti, na lilijumuisha hifadhidata ya mtumiaji. Na kwa kuanzia, niliamua kujenga utendaji wa kufanya kazi na tovuti, ili baadaye niweze kuongeza usindikaji wa programu kwenye Android na iOS.

Hapa kuna nambari fulani
Uzi kuu unaopokea wateja kwa kitanzi kisicho na mwisho:

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

Msimamizi wa mteja mwenyewe:

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

Na hifadhidata ya kwanza iliyojengwa kwenye SQL ya ndani:

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

Kama unaweza kuona, toleo hili ni tofauti kidogo na ile iliyo kwenye kifungu. Kwa kweli, hapa tuliongeza tu upakiaji wa kurasa kutoka kwa folda kwenye kompyuta na hifadhidata (ambayo, kwa njia, haikufanya kazi katika toleo hili kwa sababu ya usanifu usio sahihi wa uunganisho).

Sura ya 2. Kupiga magurudumu

Baada ya kujaribu seva, nilifikia hitimisho kwamba hii itakuwa suluhisho bora (mharibifu: hapana), kwa huduma yetu, kwa hivyo mradi ulianza kupata mantiki.
Hatua kwa hatua, moduli mpya zilianza kuonekana na utendaji wa seva ulipanuliwa. Seva imepata kikoa cha majaribio na usimbaji fiche wa muunganisho wa SSL.

Nambari kidogo zaidi inayoelezea mantiki ya seva na usindikaji wa mteja
Toleo lililosasishwa la seva ambalo linajumuisha matumizi ya cheti.

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

Na pia kidhibiti kipya cha mteja na idhini ya 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);
}

}
}

Lakini kwa kuwa seva inaendesha pekee kwenye muunganisho wa TCP, ni muhimu kuunda moduli ambayo inaweza kutambua muktadha wa ombi. Niliamua kuwa kichanganuzi kitafaa hapa ambacho kitavunja ombi kutoka kwa mteja katika sehemu tofauti ambazo ningeweza kuingiliana nazo ili kumpa mteja majibu muhimu.

Mchanganuzi

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

Kiini chake ni kuvunja ombi katika sehemu kwa kutumia maneno ya kawaida. Tunapokea ujumbe kutoka kwa mteja, chagua mstari wa kwanza, ambao una njia na uomba url. Kisha tunasoma vichwa, ambavyo tunaweka katika safu ya fomu HeaderName=Content, na pia tunapata, ikiwa ipo, maudhui yanayoambatana (kwa mfano, querystring) ambayo sisi pia tunaweka katika safu sawa. Kwa kuongezea, mchanganuzi hugundua ikiwa mteja wa sasa ameidhinishwa na huhifadhi data yake. Maombi yote kutoka kwa wateja walioidhinishwa yana hashi ya idhini, ambayo huhifadhiwa kwenye vidakuzi, shukrani kwa hili inawezekana kutenganisha mantiki zaidi ya uendeshaji kwa aina mbili za wateja na kuwapa majibu sahihi.

Kweli, kipengele kidogo, kizuri ambacho kingefaa kuwekwa katika sehemu tofauti, ubadilishaji wa maswali kama "site.com/@UserName" kuwa kurasa za watumiaji zinazozalishwa kwa nguvu. Baada ya kushughulikia ombi, moduli zifuatazo zinatumika.

Sura ya 3. Kufunga usukani, lubrication ya mnyororo

Mara tu kichanganuzi kinapomaliza kazi yake, kidhibiti kinakuja, kikitoa maagizo zaidi kwa seva na kugawanya udhibiti katika sehemu mbili.

Mshughulikiaji rahisi

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

Kwa kweli, kuna hundi moja tu ya idhini ya mtumiaji, baada ya hapo usindikaji wa ombi huanza.

Vidhibiti vya mteja
Ikiwa mtumiaji hajaidhinishwa, basi utendaji kwa ajili yake unategemea tu maonyesho ya wasifu wa mtumiaji na dirisha la usajili wa idhini. Nambari ya mtumiaji aliyeidhinishwa inaonekana sawa, kwa hivyo sioni sababu ya kuirudia.

Mtumiaji asiyeidhinishwa

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;

}

}

}
}

Na bila shaka, mtumiaji lazima apate aina fulani ya maudhui ya ukurasa, kwa hiyo kwa majibu kuna moduli ifuatayo, ambayo ni wajibu wa kujibu maombi ya rasilimali.

MwandishiMdhibiti

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

Lakini ili kuonyesha mtumiaji wasifu wake na wasifu wa watumiaji wengine, niliamua kutumia RazorEngine, au tuseme sehemu yake. Pia inajumuisha kuchakata maombi batili na kutoa msimbo unaofaa wa hitilafu.

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

Na bila shaka, ili uthibitishaji wa watumiaji walioidhinishwa kufanya kazi, idhini inahitajika. Moduli ya uidhinishaji inaingiliana na hifadhidata. Data iliyopokelewa kutoka kwa fomu kwenye tovuti imechanganuliwa kutoka kwa muktadha, mtumiaji huhifadhiwa na kwa kurudi hupokea vidakuzi na ufikiaji wa huduma.

Moduli ya uidhinishaji

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

Na hivi ndivyo usindikaji wa hifadhidata unavyoonekana kama:

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


Na kila kitu hufanya kazi kama saa, idhini na kazi ya usajili, utendaji wa chini wa kupata huduma tayari uko na ni wakati wa kuandika maombi na kuunganisha jambo zima pamoja na kazi kuu ambazo kila kitu kinafanyika.

Sura ya 4. Kutupa baiskeli

Ili kupunguza gharama za kazi za kuandika maombi mawili kwa majukwaa mawili, niliamua kufanya jukwaa la msalaba kwenye Xamarin.Fomu. Tena, shukrani kwa ukweli kwamba iko katika C #. Baada ya kufanya programu ya majaribio ambayo hutuma data kwa seva tu, niligundua jambo la kufurahisha. Kwa ombi kutoka kwa kifaa, kwa kujifurahisha, niliitekeleza kwenye HttpClient na kuituma kwa seva ya HttpRequestMessage, ambayo ina data kutoka kwa fomu ya idhini katika muundo wa json. Bila kutarajia chochote, nilifungua logi ya seva na nikaona ombi kutoka kwa kifaa na data yote. Kusinzia kidogo, ufahamu wa kila kitu ambacho kimefanywa kwa wiki 3 zilizopita katika jioni ya uchovu. Ili kuangalia usahihi wa data iliyotumwa, nilikusanya seva ya majaribio kwenye HttpListner. Baada ya kupokea ombi lingine tayari juu yake, niliitenga katika safu kadhaa za nambari na nikapokea KeyValuePair ya data kutoka kwa fomu. Uchanganuzi wa hoja ulipunguzwa hadi mistari miwili.

Nilianza kupima zaidi, haikutajwa hapo awali, lakini kwenye seva ya awali pia nilitekeleza mazungumzo yaliyojengwa kwenye websockets. Ilifanya kazi vizuri, lakini kanuni yenyewe ya mwingiliano kupitia Tcp ilikuwa ya kufadhaisha; kazi nyingi isiyo ya lazima ilibidi kufanywa ili kujenga mwingiliano wa watumiaji wawili kwa logi ya mawasiliano. Hii ni pamoja na kuchanganua ombi la kubadili uunganisho na kukusanya jibu kwa kutumia itifaki ya RFC 6455. Kwa hiyo, katika seva ya mtihani, niliamua kuunda uunganisho rahisi wa websocket. Kwa kujifurahisha tu.

Unganisha kwenye gumzo

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

Na ilifanya kazi. Seva yenyewe ilisanidi muunganisho na ikatoa kitufe cha kujibu. Sikuhitaji hata kusanidi usajili wa seva kando kupitia SSL; ilitosha kwamba mfumo tayari ulikuwa na cheti kilichowekwa kwenye bandari inayohitajika.

Kwa upande wa kifaa na upande wa tovuti, wateja wawili walibadilishana ujumbe, yote haya yameingia. Hakuna vichanganuzi vikubwa vinavyopunguza kasi ya seva, hakuna hata kimoja kati ya hivi kilichohitajika. Wakati wa kujibu umepungua kutoka 200ms hadi 40-30ms. Na nilikuja kwa uamuzi sahihi tu.

Jengo la kina la baisikeli au programu ya seva ya mteja kulingana na mfumo wa C# .Net

Tupa utekelezaji wa seva ya sasa kwenye Tcp na uandike upya kila kitu chini ya Http. Sasa mradi uko katika hatua ya kuunda upya, lakini kulingana na kanuni tofauti kabisa za mwingiliano. Uendeshaji wa vifaa na tovuti husawazishwa na kutatuliwa na ina dhana ya kawaida, na tofauti pekee ni kwamba hakuna haja ya kuzalisha kurasa za HTML za vifaa.

Pato

"Ikiwa hujui kivuko, usiingie majini" Nadhani kabla ya kuanza kazi, ninapaswa kuwa na malengo na malengo yaliyofafanuliwa wazi zaidi, na pia kuzama katika utafiti wa teknolojia na njia muhimu za utekelezaji wao kwa wateja anuwai. Mradi tayari unakaribia kukamilika, lakini labda nitarudi kuzungumza kuhusu jinsi nilivyohifadhi vitu fulani tena. Nilijifunza mengi wakati wa mchakato wa maendeleo, lakini kuna mengi zaidi ya kujifunza katika siku zijazo. Ikiwa umesoma hadi hapa, asante kwa kufanya hivyo.

Chanzo: mapenzi.com