Construction avancée de vélos ou application client-serveur basée sur C# .Net framework

Introduction

Tout a commencé lorsqu'un collègue m'a proposé de créer un petit service web. Cela devait ressembler à quelque chose comme Tinder, mais pour la communauté IT. La fonctionnalité est très simple : tu t'inscris, complètes ton profil et passes à l'essentiel, c'est-à-dire à la recherche de correspondants, à l'élargissement de tes connexions et à la création de nouvelles rencontres.

Ici, je dois faire une parenthèse et parler un peu de moi, pour que l'on comprenne mieux pourquoi j'ai pris ces décisions dans le développement.

Actuellement, je suis artiste technique dans un studio de jeux, et mon expérience en programmation C# s'est limitée à l'écriture de scripts et d'utilitaires pour Unity, ainsi qu'à la création de plugins pour le travail de bas niveau avec des appareils Android. Je n'ai pas encore exploré au-delà de cet univers, et c'est là qu'une opportunité s'est présentée.

Partie 1. Prototypage de la structure

Après avoir décidé de ce que serait ce service, j'ai commencé à chercher des options de mise en œuvre. Le plus simple aurait été de trouver une solution prête à l'emploi, sur laquelle, comme un hibou sur un globe, nous pourrions adapter nos mécaniques et mettre tout cela à la critique publique.
Mais ce n'est pas intéressant, je ne voyais aucun défi ni sens dans cela, et donc j'ai commencé à étudier les technologies web et les méthodes d'interaction avec elles.

J'ai commencé par consulter des articles et de la documentation sur C# .Net. J'y ai trouvé diverses façons de réaliser la tâche. Il existe de nombreux mécanismes d'interaction avec le réseau, des solutions complètes comme ASP.Net ou les services Azure, à l'interaction directe via des connexions TcpHttp.

Après avoir fait un premier essai avec ASP, je l'ai rejeté immédiatement, car à mon avis, c'était une solution trop complexe pour notre service. Nous n'utiliserons même pas le tiers des capacités de cette plateforme, donc j'ai continué mes recherches. Le choix s'est porté entre un client/serveur TCP et Http. C'est ici, sur Habr, que je suis tombé sur un article sur un serveur multithreadé, que j'ai monté et testé, et j'ai décidé de me concentrer sur l'interaction avec les connexions TCP, car je pensais que le http ne me permettrait pas de créer une solution multiplateforme.

La première version du serveur comprenait la gestion des connexions, servait du contenu statique de pages web et incluait une base de données utilisateurs. Pour commencer, j'ai décidé de construire la fonctionnalité pour travailler avec le site, afin de pouvoir ensuite intégrer le traitement d'une application sur Android et iOS.

Voici un peu de code
Le flux principal, dans une boucle infinie acceptant des clients :

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

Le gestionnaire de clients lui-même :

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 =

"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 =

"image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType =

"application/{Extension.Substring(1)}";
}
sinon
{
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 =

"HTTP/1.1 200 OK\nContent-Type: {ContentType}\nContent-Length: {FS.Length}\n\n";
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();
}
}
}


Et la première base de données construite sur SQL local :
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 users = db.GetTable();
                foreach (var item in users)
                {
                    Console.WriteLine("{item.login} {item.password}");

"{item.login} {item.password}"
}
}
}
}
}


Comme on peut le voir, cette version ne diffère guère de celle qui était dans l'article. En principe, il n'y a que le chargement des pages depuis un dossier sur l'ordinateur et la base de données (qui, soit dit en passant, ne fonctionne pas dans cette version, en raison d'une architecture de connexion incorrecte).

Chapitre 2. Ajout de fonctionnalités

Après avoir testé le fonctionnement du serveur, j'en ai conclu que ce serait une excellente solution (spoiler : non), pour notre service, donc le projet a commencé à se doter de logique.
Pas à pas, de nouveaux modules ont commencé à apparaître et la fonctionnalité du serveur s'est étendue. Le serveur a acquis un domaine de test et ssl un cryptage de connexion.

Encore un peu de code décrivant la logique du fonctionnement du serveur et le traitement des clients
Une version mise à jour du serveur, incluant l'utilisation d'un certificat.

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("Démarrage du serveur.." + 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("Changement de domaine");
                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();
        }
    }
}

Et aussi un nouveau gestionnaire de client avec authentification 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" +

"|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Échec de l'authentification - fermeture de la connexion.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}

}
}


Mais comme le serveur fonctionne exclusivement sur une connexion TCP, il est nécessaire de créer un module capable de reconnaître le contexte de la demande. J'ai décidé qu'un parseur qui diviserait la demande du client en parties distinctes, avec lesquelles je pourrais interagir pour fournir les réponses nécessaires au client, serait approprié.

Parseur

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 HeadersValues;
        public List FormValues;
        private TcpClient TcpClient;

        private event Action 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("{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;
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
try
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
{
CurrentUser = databaseWorker.CookieValidate(cookie);
try
{
catch { }
}
private List HeaderValues()
}
}
private List HeaderValues()

}
var values = new List();
{
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 ContentValues()
}
}
var output = Message.Trim('\n').Split().Last();
{
var parse = Regex.Matches(Message, @"(.*?): (.*?)\n");
var parse = Regex.Matches(output, @"([^&].*?)=([^&]*b)");
Value = match.Groups[2].Value.Trim().Replace('+', ' ')
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
{
Value = match.Groups[2].Value.Trim()
}
});
}
private void ProfileParse()
}
}
}


L'essence de cela est de diviser la requête en parties à l'aide d'expressions régulières. Nous recevons un message du client, extrayons la première ligne contenant la méthode et l'URL de la requête. Ensuite, nous lisons les en-têtes, que nous plaçons dans un tableau au format NomEnTête=Contenu, et nous trouvons également, s'il y en a un, le contenu d'accompagnement (par exemple, le querystring) que nous plaçons également dans un tableau similaire. De plus, le parser détermine si le client actuel est autorisé et conserve ses données. Toutes les requêtes des clients autorisés contiennent un hash d'autorisation, qui est stocké dans les cookies, ce qui permet de séparer la logique de traitement pour deux types de clients et de leur fournir des réponses appropriées.

Et une petite fonctionnalité agréable qui vaudrait la peine d'être isolée dans un module distinct, la transformation des requêtes de type « site.com/@UserName » en pages utilisateurs générées dynamiquement. Une fois la requête traitée, les modules suivants entrent en jeu.

Chapitre 3. Installation du volant, lubrification de la chaîne

Dès que le parser a fonctionné, un gestionnaire entre en jeu, donnant des instructions ultérieures au serveur et divisant le contrôle en deux parties.

Gestionnaire simple

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

En fait, ici, il n'y a qu'une seule vérification de l'autorisation de l'utilisateur, après quoi le traitement de la requête commence.

Contrôleurs de clients
Si l'utilisateur n'est pas autorisé, alors les fonctionnalités qu'il a sont limitées à l'affichage des profils d'utilisateur et à la fenêtre d'inscription/autorisation. Le code pour un utilisateur autorisé est à peu près le même, donc je ne vois pas l'intérêt de le dupliquer.

Utilisateur non autorisé

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 =

"HTTP/1.1 200 OK\nContent-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;

}

}

}
}


Bien sûr, l'utilisateur doit recevoir un certain contenu des pages, donc pour les réponses, il existe le module suivant, responsable de la réponse aux demandes de ressources.

WriterController

utilisant System;
utilisant System.IO;
utilisant System.Net.Security;
utilisant 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 =

"{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);
}
private List HeaderValues()
}

public string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
retourner

"{Header} text/html";
case ".css":
retourner

"{Header} text/css";
case ".js":
retourner

"{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
retourner

"{Header} image/{extension}";
default:
if (extension.Length > 1)
{
retourner

"{Header} application/" + extension.Substring(1);
}
sinon
{
retourner

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


Mais pour afficher à l'utilisateur son profil et les profils d'autres utilisateurs, j'ai décidé d'utiliser RazorEngine, ou plutôt une de ses parties. Il inclut également le traitement des demandes incorrectes et la génération du code d'erreur correspondant.

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

Et bien sûr, pour que la vérification des utilisateurs autorisés fonctionne, une authentification est nécessaire. Le module d'authentification interagit avec la base de données. Les données reçues des formulaires sur le site sont analysées à partir du contexte, l'utilisateur est enregistré et reçoit en retour un cookie et un accès au service.

Module d'authentification

utiliser ClearServer.Core.Cookies;
utiliser ClearServer.Core.Requester;
utiliser ClearServer.Core.Security;
utiliser System;
utiliser System.Linq;
utiliser System.Net.Security;
utiliser 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(

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

}
sinon
{
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(

"{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes(

"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);
}
sinon
{
RazorController.ErrorLoader(401);
}
}
}
}


Voici comment le traitement de la base de données se présente :

Base de données

using ClearServer.Core.UserController;
using System;
using System.Data.Linq;
using System.Linq;

namespace ClearServer
{
    class DatabaseWorker
    {

        private readonly Table users = null;
        private readonly DataContext DataBase = null;
        private const string connectionStr = @"cheminverslabase";

        public DatabaseWorker()
        {
            DataBase = new DataContext(connectionStr);
            users = DataBase.GetTable();
        }

        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("User {user.name} with id {user.uid} added");
            }

User {user.name} avec id {user.uid} ajouté
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("Le login existe déjà");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(

Console.WriteLine("User {UserToUpdate.name} avec id {UserToUpdate.uid} mis à jour");
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;
}
sinon
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}


Et tout fonctionne comme sur des roulettes, l'authentification et l'enregistrement fonctionnent, un minimum de fonctionnalité d'accès au service est déjà en place et il est temps d'écrire l'application et d'intégrer tout cela avec les fonctions principales pour lesquelles tout cela est fait.

Chapitre 4. Donc, construire une roue

Pour réduire le temps consacré à l'écriture de deux applications pour deux plateformes, j'ai décidé de créer une solution multiplateforme avec Xamarin.Forms. Encore une fois, grâce au fait qu'elle est basée sur C#. Après avoir développé une application de test qui envoie simplement des données au serveur, j'ai rencontré un point intéressant. Pour la requête depuis l'appareil, par curiosité, je l'ai implémentée avec HttpClient et j'ai envoyé au serveur un HttpRequestMessage contenant les données du formulaire d'authentification au format json. Ne m'attendant pas à grand-chose, j'ai ouvert le log du serveur et j'y ai vu la requête en provenance de l'appareil avec toutes les données. Un léger stupéfait, la prise de conscience de tout ce qui avait été réalisé au cours des trois dernières semaines de soirées harassantes. Pour vérifier l'exactitude des données envoyées, j'ai créé un serveur de test avec HttpListener. En recevant une nouvelle requête sur celui-ci, j'ai pu la décomposer en quelques lignes de code, obtenir un KeyValuePair des données du formulaire. L'analyse de la requête s'est réduite à deux lignes.

J'ai commencé à tester davantage. Il n'avait pas été mentionné auparavant, mais sur le serveur précédent, j'avais également mis en place un chat basé sur des WebSockets. Ça fonctionnait assez bien, mais le principe même de l'interaction via TCP était décourageant, il fallait produire trop de code pour établir correctement l'interaction entre deux utilisateurs avec la tenue d'un log de la conversation. Cela incluait le parsing de la requête concernant le changement de connexion et la collecte des réponses selon le protocole RFC 6455. C'est pourquoi sur le serveur de test, j'ai décidé de créer une simple connexion WebSocket, juste par curiosité.

Connexion au chat

 private static async void HandleWebsocket(HttpListenerContext context)
        {
            var socketContext = await context.AcceptWebSocketAsync(null);
            var socket = socketContext.WebSocket;
            Locker.EnterWriteLock();
            try
            {
                Clients.Add(socket);
            }
            finally
            {
                Locker.ExitWriteLock();
            }

            while (true)
            {
                var buffer = new ArraySegment(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();
                        }
                    }
                }
            }
        }

Et ça a fonctionné. Le serveur a configuré la connexion de lui-même et a généré la clé de réponse. Je n'ai même pas eu besoin de configurer séparément l'enregistrement du serveur avec ssl, il suffisait que le certificat soit déjà installé sur le port requis.

Du côté de l'appareil et du site, deux clients échangeaient des messages, tout cela était logué. Il n'était pas nécessaire d'utiliser d'énormes parseurs, ralentissant le serveur. Le temps de réponse a été réduit de 200 ms à 30-40 ms. J'en suis venu à la seule solution correcte.

Construction avancée de vélos ou application client-serveur basée sur C# .Net framework

Jeter l'implémentation actuelle du serveur sur Tcp et tout réécrire sous Http. Le projet est maintenant en phase de réingénierie, mais selon des principes de collaboration complètement différents. Le fonctionnement des dispositifs et du site est synchronisé et ajusté avec un concept commun, avec la seule différence qu'il n'est pas nécessaire de générer des pages html pour les dispositifs.

Sortie

« Ne te jette pas à l'eau sans connaître le courant » Je pense que, avant de commencer le travail, j'aurais dû définir plus clairement les objectifs et les tâches, ainsi que plonger plus profondément dans l'étude des technologies et des méthodes nécessaires à leur mise en œuvre sur différents clients. Le projet est déjà presque terminé, mais il est possible que je revienne pour parler de la façon dont j'ai de nouveau fait des erreurs. J'ai beaucoup appris au cours du processus de développement, mais il me reste encore beaucoup à apprendre à l'avenir. Si vous avez lu jusqu'ici, merci pour cela.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster