Gevorderde fietsry of kliënt-bediener toepassing gebaseer op C# .Net raamwerk

Entry

Dit het alles begin toe 'n kollega voorgestel het dat ek 'n klein webdiens maak. Dit was veronderstel om iets soos 'n tinder te wees, maar vir 'n IT-kuierplek. Die funksionaliteit is heeltemal eenvoudig, jy registreer, vul 'n profiel in en gaan aan na die hoofpunt, naamlik om 'n gespreksgenoot te vind en jou verbindings uit te brei en nuwe kennisse te maak.

Hier moet ek afwyk en bietjie van myself vertel, sodat dit in die toekoms duideliker sal wees hoekom ek sulke stappe in ontwikkeling geneem het.

Op die oomblik beklee ek die posisie van 'n Tegniese Kunstenaar in 'n speletjie-ateljee, my C#-programmeringservaring was slegs gebaseer op die skryf van skrifte en nutsprogramme vir Unity en, benewens dit, die skep van inproppe vir lae-vlak werk met Android-toestelle. Buite hierdie klein wêreldjie het ek nog nie so 'n geleentheid gekies en toe opgedaag nie.

Deel 1. Raamprototipering

Nadat ek besluit het hoe hierdie diens sal wees, het ek begin soek na opsies vir implementering. Die maklikste manier sal wees om 'n soort klaargemaakte oplossing te vind, waarop jy, soos 'n uil op 'n aardbol, ons meganika kan trek en die hele ding vir openbare sensuur kan uitlê.
Maar dit is nie interessant nie, ek het geen uitdaging en sin hierin gesien nie, en daarom het ek begin om webtegnologieë en metodes om daarmee te kommunikeer te bestudeer.

Die studie het begin deur artikels en dokumentasie op C # .Net te kyk. Hier het ek verskeie maniere gevind om die taak uit te voer. Daar is baie meganismes vir interaksie met die netwerk, van volwaardige oplossings soos ASP.Net of Azure-dienste, tot direkte interaksie met TcpHttp-verbindings.

Nadat ek die eerste poging met ASP gemaak het, het ek dit dadelik gekanselleer, na my mening was dit 'n te moeilike besluit vir ons diens. Ons sal nie eers 'n derde van die vermoëns van hierdie platform gebruik nie, so ek het my soektog voortgesit. Die keuse het ontstaan ​​tussen TCP en Http kliënt-bediener. Hier op Habré het ek op 'n artikel afgekom oor multithreaded bediener, nadat ek dit versamel en getoets het, het ek besluit om te fokus op interaksie met TCP-verbindings, om een ​​of ander rede het ek gedink dat http my nie sou toelaat om 'n kruisplatform-oplossing te skep nie.

Die eerste weergawe van die bediener het die hantering van verbindings, die aanbieding van statiese webbladsy-inhoud en die insluiting van 'n gebruikersdatabasis ingesluit. En om mee te begin, het ek besluit om 'n funksionele te bou om met die webwerf te werk, sodat ek later die toepassingsverwerking op Android en iOS hier kon koppel.

Hier is 'n paar kode
Die hoofdraad wat kliënte in 'n eindelose lus aanvaar:

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

Die kliënt hanteerder self:

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 die eerste databasis gebou op plaaslike 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}");
}
}
}
}
}

Soos u kan sien, verskil hierdie weergawe min van die een in die artikel. Trouens, net die laai van bladsye vanaf 'n lêergids op die rekenaar en die databasis is hier bygevoeg (wat, terloops, nie in hierdie weergawe gewerk het nie, weens die verkeerde verbindingsargitektuur).

hoofstuk 2

Nadat ek die bediener getoets het, het ek tot die gevolgtrekking gekom dat dit 'n goeie oplossing sou wees (bederf: nee), vir ons diens, sodat die projek logika begin verkry het.
Stap vir stap het nuwe modules begin verskyn en die funksionaliteit van die bediener het gegroei. Die bediener het 'n toetsdomein en ssl-verbindingkodering.

'n Bietjie meer kode wat die logika van die bediener en die verwerking van kliënte beskryf
'n Opgedateerde weergawe van die bediener, insluitend die gebruik van 'n sertifikaat.

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

Sowel as 'n nuwe kliënt hanteerder met magtiging via 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);
}

}
}

Maar aangesien die bediener uitsluitlik op 'n TCP-verbinding werk, is dit nodig om 'n module te skep wat die versoekkonteks kan herken. Ek het besluit dat 'n ontleder hier geskik is wat die versoek van die kliënt sal opbreek in afsonderlike dele waarmee ek interaksie kan hê om die kliënt die nodige antwoorde te gee.

ontleder

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

Die essensie daarvan lê in die feit dat met behulp van gereelde uitdrukkings die versoek in dele te breek. Ons ontvang 'n boodskap van die kliënt, kies die eerste reël, wat die metode bevat en versoek url. Dan lees ons die opskrifte, wat ons in 'n skikking van die vorm HeaderName = Content indryf, en vind ook, indien enige, die meegaande inhoud (byvoorbeeld, navraagstring) wat ons ook in 'n soortgelyke skikking indryf. Daarbenewens vind die ontleder uit of die huidige kliënt gemagtig is en stoor sy data. Alle versoeke van gemagtigde kliënte bevat 'n magtiging-hash, wat in koekies gestoor word, waardeur dit moontlik is om verdere werklogika vir twee tipes kliënte te skei en hulle die korrekte antwoorde te gee.

Wel, 'n klein, lekker kenmerk wat na 'n aparte module geskuif moet word, wat versoeke soos "site.com/@UserName" omskakel na dinamies gegenereerde gebruikersbladsye. Nadat die versoek verwerk is, kom die volgende modules ter sprake.

Hoofstuk 3. Installeer die stuurstang, smeer die ketting

Sodra die ontleder voltooi is, kom die hanteerder in die spel, gee verdere instruksies aan die bediener en verdeel beheer in twee dele.

eenvoudige hanteerder

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

Trouens, daar is net een tjek vir gebruikermagtiging, waarna die verwerking van die versoek begin.

Kliëntbeheerders
As die gebruiker nie gemagtig is nie, is die funksionaliteit vir hom slegs gebaseer op die vertoon van gebruikersprofiele en die magtigingsregistrasievenster. Die kode vir 'n gemagtigde gebruiker lyk omtrent dieselfde, so ek sien geen rede om dit te dupliseer nie.

Ongemagtigde gebruiker

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 natuurlik moet die gebruiker 'n mate van inhoud van die bladsye ontvang, so vir antwoorde is daar die volgende module, wat verantwoordelik is om te reageer op 'n versoek om hulpbronne.

Skrywerbeheerder

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

Maar om die gebruiker sy profiel en profiele van ander gebruikers te wys, het ek besluit om RazorEngine te gebruik, of eerder deel daarvan. Dit sluit ook die hantering van slegte versoeke en die uitreiking van die toepaslike foutkode in.

Skeermesbeheerder

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 natuurlik, om die verifikasie van gemagtigde gebruikers te laat werk, is magtiging nodig. Die magtigingsmodule is in wisselwerking met die databasis. Die data wat van die vorms op die webwerf ontvang word, word uit die konteks ontleed, die gebruiker word gestoor en ontvang in ruil daarvoor koekies en toegang tot die diens.

Magtigingsmodule

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 so lyk die databasis:

databasis

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 werk soos klokslag, magtiging en registrasie werk, die minimum funksionaliteit van toegang tot die diens is reeds beskikbaar en dit is tyd om 'n aansoek te skryf en die hele ding te koppel met die hooffunksies waarvoor alles gedoen word.

Hoofstuk 4

Om die arbeidskoste van die skryf van twee aansoeke vir twee platforms te verminder, het ek besluit om 'n kruisplatform op Xamarin.Forms te maak. Weereens, te danke aan die feit dat dit in C# is. Nadat ek 'n toetstoepassing gemaak het wat bloot data na die bediener stuur, het ek een interessante oomblik teëgekom. Vir 'n versoek van die toestel, vir die pret, het ek dit op HttpClient geïmplementeer en dit op die bediener HttpRequestMessage gegooi wat data van die magtigingsvorm in json-formaat bevat. Sonder om iets spesifieks te verwag, het ek die bedienerlogboek oopgemaak en 'n versoek van die toestel met al die data daar gesien. Ligte stupor, bewustheid van alles wat gedoen is die afgelope 3 weke van lome aand. Om die korrektheid van die gestuurde data na te gaan, het ek 'n toetsbediener op HttpListner saamgestel. Nadat ek die volgende versoek reeds daarop ontvang het, het ek dit in 'n paar reëls kode uitmekaar gehaal, die KeyValuePair-data van die vorm gekry. Navraagontleding verminder tot twee reëls.

Ek het verder begin toets, dit is nie voorheen genoem nie, maar op die vorige bediener het ek steeds 'n klets geïmplementeer wat op websockets gebou is. Dit het redelik goed gewerk, maar die beginsel van interaksie via Tcp was neerdrukkend, te veel ekstra moes geproduseer word om die interaksie van twee gebruikers met die aanteken van korrespondensie korrek te bou. Dit sluit in die ontleed van 'n versoek vir verbindingskakeling en die insameling van 'n antwoord deur die RFC 6455-protokol te gebruik. Daarom het ek in die toetsbediener besluit om 'n eenvoudige websokverbinding te skep. Suiwer interessantheidshalwe.

Kletsverbinding

 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 dit het gewerk. Die bediener self het die verbinding opgestel, 'n antwoordsleutel gegenereer. Ek hoef nie eers bedienerregistrasie via ssl afsonderlik op te stel nie, dit is genoeg dat die stelsel reeds 'n sertifikaat op die vereiste poort geïnstalleer het.

Aan die toestelkant en aan die werfkant het twee kliënte boodskappe uitgeruil, dit alles is aangeteken. Geen groot ontleders wat die bediener vertraag nie, niks hiervan was nodig nie. Die reaksietyd is van 200ms tot 40-30ms verminder. En ek het tot die enigste regte besluit gekom.

Gevorderde fietsry of kliënt-bediener toepassing gebaseer op C# .Net raamwerk

Gooi die huidige bedienerimplementering op Tcp uit en herskryf alles onder Http. Nou is die projek in die stadium van herontwerp, maar volgens heeltemal ander beginsels van interaksie. Die werking van toestelle en die webwerf is gesinchroniseer en ontfout en het 'n algemene konsep, met die enigste verskil dat toestelle nie html-bladsye hoef te genereer nie.

Output

"Omdat jy nie die drif ken nie, moenie jou kop in die water steek nie" Ek dink, voordat ek begin werk, moes ek die doelwitte en doelwitte duideliker gedefinieer het, sowel as die bestudering van die nodige tegnologieë en metodes vir die implementering daarvan op verskeie kliënte. Die projek nader reeds voltooiing, maar miskien sal ek terugkom om te praat oor hoe ek weer sekere dinge opgefok het. Ek het baie tydens die ontwikkelingsproses geleer, maar daar is meer om te leer in die toekoms. As jy tot hier gelees het, dankie dat jy gelees het.

Bron: will.com