Geavanceerde cycling- of client-servertoepassing op basis van C# .Net-framework

Toegang

Het begon allemaal toen een collega me voorstelde om een ​​kleine webservice te maken. Het moest zoiets zijn als een tondel, maar dan voor een IT-ontmoetingsplaats. De functionaliteit is uiterst eenvoudig, je registreert, vult een profiel in en gaat verder met de hoofdzaak, namelijk een gesprekspartner vinden en je contacten uitbreiden en nieuwe kennissen maken.

Hier moet ik afdwalen en iets over mezelf vertellen, zodat het in de toekomst duidelijker zal zijn waarom ik zulke stappen in ontwikkeling heb gezet.

Op het moment dat ik de functie van technisch tekenaar in een gamestudio bekleed, was mijn C#-programmeerervaring alleen gebaseerd op het schrijven van scripts en hulpprogramma's voor Unity en daarnaast het maken van plug-ins voor low-level werk met Android-apparaten. Buiten deze kleine wereld heb ik nog niet zo'n kans gekozen en vervolgens aangegrepen.

Deel 1. Prototyping van frames

Nadat ik had besloten hoe deze service eruit zou zien, begon ik te zoeken naar opties voor implementatie. De gemakkelijkste manier zou zijn om een ​​soort kant-en-klare oplossing te vinden, waarop je, als een uil op een wereldbol, onze monteurs kunt trekken en het hele ding kunt neerleggen voor openbare censuur.
Maar dit is niet interessant, ik zag hier geen uitdaging en zin in en daarom begon ik webtechnologieën en methoden om ermee om te gaan te bestuderen.

Het onderzoek begon met het bekijken van artikelen en documentatie op C# .Net. Hier vond ik verschillende manieren om de taak te volbrengen. Er zijn veel mechanismen voor interactie met het netwerk, van volwaardige oplossingen zoals ASP.Net of Azure-services tot directe interactie met TcpHttp-verbindingen.

Nadat ik de eerste poging met ASP had gedaan, heb ik deze onmiddellijk geannuleerd, naar mijn mening was het een te moeilijke beslissing voor onze service. We zullen niet eens een derde van de mogelijkheden van dit platform gebruiken, dus ging ik verder met zoeken. De keuze ontstond tussen TCP en Http client-server. Hier, op Habré, kwam ik een artikel tegen over multithreaded server, nadat ik die had verzameld en getest, besloot ik me te concentreren op de interactie met TCP-verbindingen, om de een of andere reden dacht ik dat http me niet in staat zou stellen een platformonafhankelijke oplossing te creëren.

De eerste versie van de server omvatte het afhandelen van verbindingen, het aanbieden van statische webpagina-inhoud en een gebruikersdatabase. En om te beginnen besloot ik een functionaliteit te bouwen voor het werken met de site, zodat ik later de applicatieverwerking op Android en iOS hier kon koppelen.

Hier is wat code
De rode draad die klanten accepteert in een eindeloze 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 klanthandler zelf:

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

Zoals u kunt zien, verschilt deze versie weinig van die in het artikel. In feite is hier alleen het laden van pagina's uit een map op de computer en de database toegevoegd (wat in deze versie overigens niet werkte vanwege de onjuiste verbindingsarchitectuur).

Hoofdstuk 2

Na het testen van de server kwam ik tot de conclusie dat dit een geweldige oplossing zou zijn (spoiler: nee), voor onze service, dus het project begon logisch te worden.
Stap voor stap begonnen er nieuwe modules te verschijnen en groeide de functionaliteit van de server. De server heeft een testdomein en ssl-verbindingscodering.

Iets meer code die de logica van de server en de verwerking van clients beschrijft
Een bijgewerkte versie van de server, inclusief het gebruik van een certificaat.

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

Evenals een nieuwe clienthandler met autorisatie 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 aangezien de server uitsluitend op een TCP-verbinding werkt, is het noodzakelijk om een ​​module te maken die de aanvraagcontext kan herkennen. Ik besloot dat hier een parser geschikt is die het verzoek van de klant opsplitst in afzonderlijke delen waarmee ik kan communiceren om de klant de nodige antwoorden te geven.

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

De essentie ligt in het feit dat met behulp van reguliere expressies het verzoek in delen wordt opgesplitst. We ontvangen een bericht van de klant, selecteer de eerste regel, die de methode en de verzoek-URL bevat. Vervolgens lezen we de headers, die we in een array van de vorm HeaderName = Content plaatsen, en vinden we, indien aanwezig, de bijbehorende inhoud (bijvoorbeeld querystring) die we ook in een vergelijkbare array plaatsen. Daarnaast zoekt de parser of de huidige klant geautoriseerd is en slaat zijn gegevens op. Alle verzoeken van geautoriseerde klanten bevatten een autorisatie-hash, die wordt opgeslagen in cookies, waardoor het mogelijk is om verdere werklogica voor twee soorten klanten te scheiden en hen de juiste antwoorden te geven.

Welnu, een kleine, leuke functie die naar een aparte module zou moeten worden verplaatst, waarbij verzoeken zoals "site.com/@UserName" worden omgezet in dynamisch gegenereerde gebruikerspagina's. Na het verwerken van de aanvraag komen de volgende modules in beeld.

Hoofdstuk 3. Het stuur monteren, de ketting smeren

Zodra de parser is voltooid, komt de handler in het spel, die verdere instructies aan de server geeft en de controle in twee delen verdeelt.

eenvoudige behandelaar

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

In feite is er slechts één controle op gebruikersautorisatie, waarna de aanvraagverwerking begint.

Klantcontrollers
Als de gebruiker niet geautoriseerd is, is de functionaliteit voor hem alleen gebaseerd op de weergave van gebruikersprofielen en het autorisatieregistratievenster. De code voor een geautoriseerde gebruiker ziet er ongeveer hetzelfde uit, dus ik zie geen reden om deze te dupliceren.

Onbevoegde 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 natuurlijk moet de gebruiker wat inhoud van de pagina's ontvangen, dus voor antwoorden is er de volgende module, die verantwoordelijk is voor het reageren op een verzoek om bronnen.

SchrijverController

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 de gebruiker zijn profiel en profielen van andere gebruikers te laten zien, besloot ik RazorEngine te gebruiken, of liever een deel ervan. Het omvat ook het afhandelen van slechte verzoeken en het uitgeven van de juiste foutcode.

ScheermesController

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 om de verificatie van geautoriseerde gebruikers te laten werken, is er natuurlijk autorisatie nodig. De autorisatiemodule communiceert met de database. De gegevens die worden ontvangen van de formulieren op de site worden ontleed aan de context, de gebruiker wordt opgeslagen en ontvangt in ruil daarvoor cookies en toegang tot de dienst.

Autorisatie 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 zo ziet de database eruit:

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 werkt als een uurwerk, autorisatie- en registratiewerk, de minimale functionaliteit van toegang tot de service is al beschikbaar en het is tijd om een ​​applicatie te schrijven en het geheel te koppelen aan de belangrijkste functies waarvoor alles is gedaan.

Hoofdstuk 4

Om de arbeidskosten van het schrijven van twee applicaties voor twee platforms te verlagen, heb ik besloten om een ​​cross-platform op Xamarin.Forms te maken. Nogmaals, dankzij het feit dat het in C# is. Nadat ik een testapplicatie had gemaakt die simpelweg gegevens naar de server stuurt, kwam ik een interessant moment tegen. Voor een verzoek van het apparaat, voor de lol, heb ik het op HttpClient geïmplementeerd en op de server HttpRequestMessage gegooid die gegevens van het autorisatieformulier in json-indeling bevat. Zonder iets bijzonders te verwachten, opende ik het serverlogboek en zag ik een verzoek van het apparaat met alle gegevens daar. Lichte verdoving, besef van alles wat de afgelopen 3 weken lome avond is gedaan. Om de juistheid van de verzonden gegevens te controleren, heb ik een testserver op HttpListner in elkaar gezet. Nadat ik het volgende verzoek al had ontvangen, heb ik het in een paar regels code uit elkaar gehaald en de KeyValuePair-gegevens uit het formulier gehaald. Het parseren van query's teruggebracht tot twee regels.

Ik ben verder gaan testen, het was nog niet eerder genoemd, maar op de vorige server heb ik nog een chat geïmplementeerd gebouwd op websockets. Het werkte redelijk goed, maar het principe van interactie via Tcp was deprimerend, er moest te veel extra worden geproduceerd om de interactie van twee gebruikers correct op te bouwen met het loggen van correspondentie. Dit omvat het parseren van een verzoek om verbinding te wisselen en het verzamelen van een antwoord met behulp van het RFC 6455-protocol.Daarom besloot ik in de testserver een eenvoudige websocket-verbinding te maken. Puur uit interesse.

Chat-verbinding

 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 het werkte. De server zette zelf de verbinding op, genereerde een antwoordsleutel. Ik hoefde de serverregistratie niet eens apart via ssl te configureren, het volstaat dat het systeem al een certificaat op de vereiste poort heeft geïnstalleerd.

Aan de apparaatkant en aan de sitekant wisselden twee clients berichten uit, dit alles werd gelogd. Geen enorme parsers die de server vertragen, dit was allemaal niet nodig. De responstijd is teruggebracht van 200 ms naar 40-30 ms. En ik kwam tot de enige juiste beslissing.

Geavanceerde cycling- of client-servertoepassing op basis van C# .Net-framework

Gooi de huidige serverimplementatie op Tcp weg en herschrijf alles onder Http. Nu is het project bezig met een herontwerp, maar volgens totaal andere principes van interactie. De werking van apparaten en de site is gesynchroniseerd en gedebugd en heeft een gemeenschappelijk concept, met het enige verschil dat apparaten geen html-pagina's hoeven te genereren.

Uitgang

"Ik ken de doorwaadbare plaats niet, steek je hoofd niet in het water" Ik denk dat ik, voordat ik aan het werk ging, de doelen en doelstellingen duidelijker had moeten definiëren, en me ook had moeten verdiepen in de studie van de noodzakelijke technologieën en methoden voor de implementatie ervan bij verschillende klanten. Het project nadert zijn voltooiing, maar misschien kom ik nog eens terug om te vertellen hoe ik bepaalde dingen weer verknoeid heb. Ik heb veel geleerd tijdens het ontwikkelingsproces, maar er valt nog meer te leren in de toekomst. Als je tot nu toe hebt gelezen, bedankt voor het lezen.

Bron: www.habr.com