Avansearre cycling of client-tsjinner applikaasje basearre op C # .Net framework

Ynlieding

It begon allegear doe't in kollega suggerearre dat ik in lytse webtsjinst oanmeitsje. It soe sa'n ding wêze as Tinder, mar foar it IT-folk. De funksjonaliteit is ekstreem ienfâldich, jo registrearje, folje in profyl yn en gean troch nei it haadpunt, nammentlik in persoan fine om mei te praten en jo ferbiningen út te wreidzjen en nije kunde te meitsjen.

Hjir moat ik in retreat meitsje en in bytsje oer mysels fertelle, sadat it yn 'e takomst dúdliker wurde soe wêrom't ik sokke stappen yn ûntwikkeling naam.

Op it stuit hâld ik de posysje fan Technical Artist yn ien spielstudio, myn programmearringûnderfining yn C # waard allinich boud op it skriuwen fan skripts en nutsbedriuwen foar Unity en, neist dit, it meitsjen fan plugins foar wurk op leech nivo mei Android-apparaten. Ik hie my noch net bûten dizze lytse wrâld weage, en doe kaam sa'n kâns.

Diel 1. Frame prototyping

Nei't ik besletten hoe't dizze tsjinst soe wêze, begon ik te sykjen nei opsjes foar ymplemintaasje. It maklikste soe wêze om in soarte fan klearebare oplossing te finen, dêr't, as in ûle op in globe, ús meganika op lutsen wurde kin en it gehiel kin wurde bleatsteld oan iepenbiere sensuer.
Mar dit is net ynteressant, ik seach der gjin útdaging of sin yn, en dêrom begon ik webtechnologyen te studearjen en metoaden foar ynteraksje mei har.

Ik begon te studearjen troch te sjen nei artikels en dokumintaasje op C# .Net. Hjir fûn ik ferskate manieren om de taak te foltôgjen. D'r binne in protte meganismen foar ynteraksje mei it netwurk, fan folsleine oplossingen lykas ASP.Net of Azure-tsjinsten, oant direkte ynteraksje mei TcpHttp-ferbiningen.

Nei't ik myn earste besykjen mei ASP makke, wegere ik it fuortendaliks; neffens my wie dit in te lestich beslút foar ús tsjinst. Wy soene net iens in tredde fan de mooglikheden fan dit platfoarm brûke, dus ik gie troch mei myn sykjen. De kar wie tusken TCP en Http client-server. Hjir, op Habré, kaam ik in artikel tsjin oer multithreaded tsjinner, Ik haw it sammele en hifke, besleat ik spesifyk te rjochtsjen op ynteraksje mei TCP-ferbiningen, om ien of oare reden tocht ik dat http my net tastean om in cross-platform-oplossing te meitsjen.

De earste ferzje fan de tsjinner omfette ferbiningsferwurking, tsjinne statyske ynhâld fan websiden, en befette in brûkersdatabase. En om te begjinnen, besleat ik om funksjonaliteit te bouwen foar it wurkjen mei de side, sadat ik letter ferwurking fan 'e applikaasje op Android en iOS koe tafoegje.

Hjir is wat koade
De haadtried dy't kliïnten ûntfangt yn in einleaze lus:

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

De client handler sels:

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

En de earste databank boud op lokale 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}");
}
}
}
}
}

Sa't jo sjen kinne, ferskilt dizze ferzje net folle fan dy yn it artikel. Yn feite hawwe wy hjir gewoan it laden fan siden tafoege út in map op 'e kompjûter en in databank (dy't trouwens net wurke yn dizze ferzje fanwegen de ferkearde ferbiningsarsjitektuer).

Haadstik 2. Screwing de tsjillen

Nei it testen fan de server kaam ik ta de konklúzje dat dit in poerbêste oplossing soe wêze (spoiler: nee), foar ús tsjinst, sadat it projekt logika begon te krijen.
Stap foar stap begûnen nije modules te ferskinen en de funksjonaliteit fan de tsjinner útwreide. De tsjinner hat in testdomein en SSL-ferbining fersifering krigen.

In bytsje mear koade beskriuwt de logika fan de tsjinner en client ferwurking
In bywurke ferzje fan de tsjinner dy't it brûken fan in sertifikaat omfettet.

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

En ek in nije kliïnthanneler mei SSL-autorisaasje:

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

}
}

Mar om't de tsjinner allinich op in TCP-ferbining rint, is it nedich om in module te meitsjen dy't de kontekst fan it fersyk werkenne koe. Ik besleat dat hjir in parser geskikt wêze soe dy't it fersyk fan de klant ferbrekke soe yn aparte dielen dêr't ik mei omgean koe om de klant de nedige antwurden te jaan.

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

De essinsje dêrfan is om it fersyk yn dielen te brekken mei help fan reguliere útdrukkingen. Wy krije in berjocht fan 'e kliïnt, selektearje de earste rigel, dy't de metoade befettet en fersyk url. Dan lêze wy de kopteksten, dy't wy sette yn in array fan de foarm HeaderName=Ynhâld, en wy fine ek, as beskikber, begeliedende ynhâld (bygelyks querystring) dy't wy ek yn in ferlykbere array sette. Derneist fynt de parser út oft de hjoeddeistige klant autorisearre is en bewarret syn gegevens. Alle oanfragen fan autorisearre kliïnten befetsje in autorisaasje-hash, dy't wurdt opslein yn cookies, tanksij dit is it mooglik om fierdere bestjoeringslogika te skieden foar de twa soarten kliïnten en har de juste antwurden te jaan.

No, in lytse, moaie funksje dy't it wurdich wêze soe om yn in aparte module te setten, de konverzje fan queries lykas "site.com/@UserName" yn dynamysk oanmakke brûkerssiden. Nei it ferwurkjen fan it fersyk komme de folgjende modules yn it spul.

Haadstik 3. Ynstallaasje fan it stjoer, lubrication fan de ketting

Sadree't de parser hat foltôge syn wurk, de handler komt yn toanielstik, jaan fierdere ynstruksjes oan de tsjinner en ferdiele kontrôle yn twa dielen.

Ienfâldige 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);
            };
        }
    }
}

Eins is d'r mar ien kontrôle foar brûkersautorisaasje, wêrnei't de ferwurking fan it fersyk begjint.

Client controllers
As de brûker net autorisearre is, dan is de funksjonaliteit foar him allinich basearre op it werjaan fan brûkersprofilen en it finster fan autorisaasjeregistraasje. De koade foar in autorisearre brûker sjocht der sawat itselde út, dus ik sjoch gjin reden om it te duplikearjen.

Net autorisearre brûker

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;

}

}

}
}

En fansels moat de brûker in soarte fan side-ynhâld krije, dus foar antwurden is d'r de folgjende module, dy't ferantwurdlik is foar it reagearjen op boarneoanfragen.

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

Mar om de brûker syn profyl en de profilen fan oare brûkers sjen te litten, besleat ik RazorEngine te brûken, of leaver in diel dêrfan. It omfettet ek it ferwurkjen fan ûnjildige oanfragen en it útjaan fan in passende flaterkoade.

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

En fansels, om de ferifikaasje fan autorisearre brûkers te wurkjen, is autorisaasje nedich. De autorisaasjemodule ynteraksje mei de databank. De ûntfongen gegevens fan formulieren op 'e side wurde parsed út' e kontekst, de brûker wurdt bewarre en yn ruil krijt cookies en tagong ta de tsjinst.

Autorisaasje module

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

En dit is hoe databankferwurking derút sjocht:

Databank

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


En alles wurket lykas klokwurk, autorisaasje- en registraasjewurk, de minimale funksjonaliteit foar tagong ta de tsjinst is der al en it is tiid om in applikaasje te skriuwen en it heule ding te ferbinen mei de haadfunksjes wêrfoar alles wurdt dien.

Haadstik 4. Smyt de fyts

Om de arbeidskosten te ferminderjen fan it skriuwen fan twa applikaasjes foar twa platfoarms, besleat ik in cross-platfoarm te meitsjen op Xamarin.Forms. Nochris, tank oan it feit dat it is yn C #. Nei't ik in testapplikaasje makke dy't gewoan gegevens nei de tsjinner stjoert, kaam ik in nijsgjirrich punt tsjin. Foar in fersyk fan in apparaat, foar wille, ik ymplementearre it op HttpClient en stjoerde it nei de HttpRequestMessage-tsjinner, dy't gegevens befettet fan it autorisaasjeformulier yn json-formaat. Sûnder benammen wat te ferwachtsjen iepene ik it serverlog en seach dêr in fersyk fan it apparaat mei alle gegevens. In lichte stupor, bewustwêzen fan alles wat dien is yn 'e ôfrûne 3 wiken yn in lompe jûn. Om de krektens fan 'e ferstjoerde gegevens te kontrolearjen, haw ik in testtsjinner gearstald op HttpListner. Nei't ik der al in oar fersyk op krige, naam ik it útinoar yn in pear rigels koade en krige in KeyValuePair fan gegevens út it formulier. It parsearjen fan de query waard fermindere ta twa rigels.

Ik begon fierder te testen, it waard net earder neamd, mar op 'e foarige tsjinner haw ik ek in petear útfierd op websockets boud. It wurke frij goed, mar it tige prinsipe fan ynteraksje fia Tcp wie deprimearjend; tefolle ûnnedige wurk moast dien wurde om de ynteraksje fan twa brûkers mei in korrespondinsjelog op kompetinte te bouwen. Dit omfettet it parsearjen fan in fersyk om de ferbining te wikseljen en in antwurd te sammeljen mei it protokol RFC 6455. Dêrom haw ik yn 'e testtsjinner besletten om in ienfâldige websocketferbining te meitsjen. Gewoan foar de aardichheid.

Ferbine mei petear

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

En it wurke. De tsjinner sels konfigurearre de ferbining en generearre in antwurd kaai. Ik hoegde de serverregistraasje net iens apart te konfigurearjen fia SSL; it wie genôch dat it systeem al in sertifikaat hie ynstalleare op de fereaske poarte.

Oan 'e apparaatkant en oan' e side side wikselen twa kliïnten berjochten út, dit alles waard oanmeld. Gjin enoarme parsers fertrage de tsjinner, neat fan dit wie nedich. Responstiid is ôfnommen fan 200ms nei 40-30ms. En ik kaam ta it ienige goede beslút.

Avansearre cycling of client-tsjinner applikaasje basearre op C # .Net framework

Smyt de hjoeddeistige tsjinner ymplemintaasje op Tcp út en skriuw alles ûnder Http. No is it projekt yn 'e werynrjochtingstadium, mar neffens folslein oare prinsipes fan ynteraksje. De wurking fan apparaten en de side is syngronisearre en debuggen en hat in mienskiplik konsept, mei it ienige ferskil dat d'r gjin ferlet is om HTML-siden foar apparaten te generearjen.

konklúzje

"As jo ​​​​de feart net kenne, gean dan net yn it wetter" Ik tink dat ik foardat it wurk begjinne moat dúdliker definieare doelen en doelstellingen hawwe, en ek dûke yn 'e stúdzje fan' e nedige technologyen en metoaden foar har ymplemintaasje op ferskate kliïnten. It projekt is al tichterby klear, mar miskien kom ik werom om te praten oer hoe't ik guon dingen wer bewarre haw. Ik learde in protte tidens it ûntwikkelingsproses, mar d'r is noch mear te learen yn 'e takomst. As jo ​​​​sa fier lêzen hawwe, tank foar it dwaan.

Boarne: www.habr.com