Avanceret cykelfremstilling eller klient-serverapplikation baseret på C# .Net framework

Introduktion

Det hele begyndte med, at en kollega foreslog, at jeg skulle lave en lille webservice. Det skulle blive noget, der minder om Tinder, men for IT-miljøet. Funktionaliteten er ekstremt simpel; du registrerer dig, udfylder din profil og går videre til essensen, nemlig at finde en samtalepartner, udvide dine forbindelser og få nye bekendtskaber.

Her må jeg tage et skridt tilbage og fortælle lidt om mig selv, så det senere bliver klarere, hvorfor jeg har truffet de specifikke beslutninger i udviklingen.

I øjeblikket arbejder jeg som Technical Artist i et spilstudie, og min erfaring med programmering i C# er primært opbygget omkring at skrive scripts og værktøjer til Unity samt at skabe plugins til lavniveauarbejde med Android-enheder. Uden for denne lille verden har jeg ikke bevæget mig, og nu dukkede denne mulighed op.

Del 1. Prototyping af rammen

Da jeg besluttede, hvordan denne service skulle se ud, begyndte jeg at lede efter muligheder for implementering. Det ville være nemmest at finde en eller anden færdig løsning, som vi kunne tilpasse vores mekanikker til og lægge hele dette projekt ud til offentlig bedømmelse.
Men det var jo ikke interessant; der var ikke nogen udfordring, og jeg så ikke mening i det, så jeg begyndte at studere webteknologier og metoder til at interagere med dem.

Mit studie begyndte med at læse artikler og dokumentation om C# .Net. Her fandt jeg forskellige metoder til at løse opgaven. Der er mange mekanismer til interaktion med nettet, fra omfattende løsninger som ASP.Net eller Azure-tjenester til direkte interaktion med TcpHttp-forbindelser.

Efter at have gjort mit første forsøg med ASP, skubbede jeg det straks til side; efter min mening var det en alt for tung løsning til vores service. Vi ville ikke benytte os af en tredjedel af mulighederne på denne platform, så jeg fortsatte med at lede. Valget stod mellem TCP og Http-klient-server. multitrådet server, som jeg samlede og testede, og jeg besluttede mig for at fokusere på interaktionen med TCP-forbindelser, af en eller anden grund mente jeg, at http ikke ville tillade mig at skabe en platformuafhængig løsning.

Den første version af serveren inkluderede håndtering af forbindelser, leverede statisk indhold af websteder og omfattede en database med brugere. Og for at starte besluttede jeg mig for at bygge funktionaliteten til at arbejde med hjemmesiden, så jeg senere kunne tilføje håndtering af applikationen til Android og iOS.

Her er lidt kode
Hovedtråden, der i en uendelig løkke modtager klienter:

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

Selve klienthåndtereren:

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, @"^\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);
{
string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));
return;
}

string ContentType = "";

switch (Extension)

case ".htm":
{
case ".html:":
ContentType = "text/html";
case ".css:":
break;
ContentType = "text/css";
case ".js:":
break;
ContentType = "text/javascript";
case ".jpg:":
break;
ContentType = "image/jpeg";
case ".jpeg:":
break;
case ".png:":
case ".gif:":
ContentType = "image/{Extension.Substring(1)}";
default:

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

FileStream FS;
}
ellers
{
FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
break;
}

catch (Exception)
try
{
ErrorWorker.SendError(Client, 500);
}
string Headers =
{
string.Format("HTTP/1.1 200 OK\nContent-Type: {ContentType}\nContent-Length: {FS.Length}\n\n");
return;
}

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

}
}
}
}
}


Og den første database bygget på lokal 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 users = db.GetTable();
                foreach (var item in users)
                {
                    Console.WriteLine("{item.login} {item.password}");

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


Som man kan se, adskiller denne version sig ikke meget fra den, der var i artiklen. I bund og grund er der kun tilføjet indlæsning af sider fra en mappe på computeren og en database (som for øvrigt ikke fungerede i denne version på grund af den forkerte arkitektur af forbindelsen).

Kapitel 2. Tilføjelse af funktioner

Efter at have testet serverens arbejde kom jeg til den konklusion, at det ville være en fremragende løsning (spoiler: nej), for vores service, så projektet begyndte at få mere logik.
Skridt for skridt begyndte der at komme nye moduler, og serverens funktionalitet voksede. Serveren fik et testdomæne og ssl kryptering af forbindelsen.

Endnu lidt kode, der beskriver serverens logik og behandling af klienter
Den opdaterede version af serveren, der inkluderer brugen af certifikat.

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("Starter 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("Skifter til et andet domæne");
                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();
        }
    }
}

Og også den nye klienthåndterer med SSL-godkendelse:

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(
                    "---------------------------------------------------------------------
" +

"|{DateTime.Now:g} |------------ |{Client.Client.RemoteEndPoint} |------------ |Exception: {e.Message} |------------ |Authentication failed - closing the connection. " +
"--------------------------------------------------------------------- ");
SSlClientStream.Close();
}
}
new RequestContext(SSlClientStream, Client);
}

}
}


Men da serveren kun fungerer på TCP-forbindelser, er det nødvendigt at oprette et modul, der kan genkende konteksten af anmodningen. Jeg besluttede mig for, at der her ville være brug for en parser, der ville opdele anmodningen fra klienten i separate dele, som jeg kan interagere med for at give klienten de ønskede svar.

Parser

using ClearServer.Core.UserController;
using ReServer.Core.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer.Core.Requester
{
    public class RequestContext
    {
        public string Message = "";
        private readonly byte[] buffer = new byte[1024];
        public string RequestMethod;
        public string RequestUrl;
        public User RequestProfile;
        public User CurrentUser = null;
        public List 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}");

"n{DateTime.Now:g} Klient 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 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 values = new List();
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;
}
}
}


Essensen er, at vi ved hjælp af regulære udtryk opdeler forespørgslen i dele. Vi modtager en meddelelse fra klienten, identificerer den første linje, der indeholder metoden og URL'en for forespørgslen. Derefter læser vi overskrifterne, som vi gemmer i et array i formatet NavnPunkt=Indhold, og vi finder også eventuelt ledsagende indhold (f.eks. querystring), som vi også placerer i et tilsvarende array. Desuden afgør parseren, om den aktuelle klient er autoriseret, og gemmer dens data. Alle forespørgsler fra autoriserede brugere indeholder en autoriseringshash, der er gemt i cookies, hvilket gør det muligt at adskille den videre logik for de to typer af kunder og give dem de korrekte svar.

Og en lille, nyttig funktion, som ville være værd at adskille i en egen modul, er omsætningen af forespørgsler i stil med „site.com/@UserName“ til dynamisk genererede bruger sider. Efter behandlingen af forespørgslen træder de følgende moduler i kraft.

Kapitel 3. Installation af styret, smøring af kæden

Så snart parseren har arbejdet færdig, træder behandleren i kraft og giver yderligere instrukser til serveren og opdeler styringen i to dele.

En simpel behandler

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

I princippet er der kun én kontrol af brugerens autorisation, hvorefter behandlingen af forespørgslen påbegyndes.

Klientcontrollerne
Hvis brugeren ikke er autoriseret, er funktionaliteten for ham baseret udelukkende på visning af brugernes profiler og registrerings- og autoriséringsvinduet. Koden for den autoriserede bruger ligner næsten den ikke-autoriserede, så jeg ser ikke mening i at duplikere den.

Ikke-autoriseret bruger

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;
ContentType = "application/{Extension.Substring(1)}";
//в данном блоке кода происходит отсечение запросов к серверу по прямому адресу страницы вида 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;

}

}

}
}


Og selvfølgelig skal brugeren have noget indhold fra siderne, så der findes følgende modul, der håndterer svaret på anmodningen om ressourcer.

WriterController

using System;
using System.IO;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    public class WriteController
    {
        SslStream ClientStream;
        public WriteController(SslStream ClientStream)
        {
            this.ClientStream = ClientStream;
        }

        public void DefaultWriter(string Header, string FilePath)
        {
            FileStream fileStream;
            try
            {
                fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                Header =

quot;{Header}nContent-Length: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = new byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
catch { }
}

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

quot;{Header} text\/html";
ContentType = "text/css";
returner

quot;{Header} text\/css";
ContentType = "text/javascript";
returner

quot;{Header} text\/javascript";
ContentType = "image/jpeg";
case ".png:":
case ".gif:":
ContentType = "image/{Extension.Substring(1)}";
returner

quot;{Header} image\/{extension}";
ContentType = "application/{Extension.Substring(1)}";
if (extension.Length > 1)
{
returner

quot;{Header} application\/" + extension.Substring(1);
}
ellers
{
returner

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


Men for at vise brugeren sin profil og profilerne for andre brugere besluttede jeg at bruge RazorEngine, nærmere bestemt en del af den. Det inkluderer også behandling af ugyldige forespørgsler og udstedelse af den relevante fejlkode.

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

Og selvfølgelig, for at kontrollerer autoriserede brugere, er der behov for autentificering. Autentificeringsmodulet interagerer med databasen. De data, der er indsamlet fra formularerne på webstedet, parses fra konteksten, brugeren gemmes og modtager cookies og adgang til tjenesten.

Autentificeringsmodul

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(

"HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);

}
ellers
{
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 Permanently\nLocation: /@{user.login}\nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
ellers
{
RazorController.ErrorLoader(401);
}
}
}
}


Og sådan ser databasebehandlingen ud:

Database

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 = @"path_to_database";

        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");
            }
            catch (Exception)
            {
                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("User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
        }

        public User CookieValidate(string CookieInput)
        {
            User user = null;
            user = users.SingleOrDefault(x => x.cookie == CookieInput);
            if (user != null) return user;
            else return null;
        }

        public User FindUser(string login)
        {
            user = users.Single(x => x.login.ToLower() == login.ToLower());
            if (user != null)
                return user;
        }
    }
}

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(

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;
}
ellers
{
return null;
}
}
string Headers =
{
return null;
}
}
}
}


Og alt fungerer perfekt; autentificering og registrering fungerer, der er allerede minimal funktionalitet til adgang til tjenesten, og det er tid til at skrive applikationen og integrere alt dette med de hovedfunktioner, som det hele handler om.

Kapitel 4. At genopfinde hjulet

For reducing the labor costs of developing two applications for two platforms, I decided to create a cross-platform solution using Xamarin.Forms. Again, thanks to the fact that it is built on C#. After making a test application that simply sends data to the server, I encountered an interesting moment. For the request from the device, I implemented it using HttpClient and sent an HttpRequestMessage to the server, which contained the data from the authorization form in JSON format. Without expecting much, I opened the server log and saw there the request from the device with all the data. A slight pause, realizing everything that had been accomplished over the past three weeks of long evenings. To verify the accuracy of the sent data, I set up a test server using HttpListener. Receiving another request on it, I parsed it into parts with a couple of lines of code, extracting KeyValuePair data from the form. The request parsing was reduced to two lines.

I began testing further. It was not mentioned before, but on the previous server, I also implemented a chat built on WebSockets. It worked quite well, but the very principle of interaction through TCP was frustrating; I had to create too much unnecessary code to properly establish the interaction between two users while keeping a log of the conversation. This includes parsing the request for switching connections and gathering responses according to RFC 6455. Therefore, on the test server, I decided to create a simple WebSocket connection. Just out of curiosity.

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

Og det virkede. Serveren opsatte forbindelsen selv og genererede en svarnøgle. Jeg behøvede ikke engang at indstille serverregistreringen for SSL separat, det var tilstrækkeligt, at et certifikat var installeret på det rigtige port i systemet.

På enheden og på hjemmesiden udvekslede to klienter beskeder, og alt dette blev logget. Ingen enorme parser, der bremser serverens arbejde, var nødvendige. Svarstiden blev reduceret fra 200 ms til 40-30 ms. Jeg kom frem til den eneste korrekte løsning.

Avanceret cykelfremstilling eller klient-serverapplikation baseret på C# .Net framework

Forkaste den nuværende implementering af serveren på Tcp og omskrive alt til Http. Projektet er nu i en omdesignfase, men efter helt andre samarbejdsprincipper. Drift af enhederne og hjemmesiden er synkroniseret og optimeret og har et fælles koncept, hvor den eneste forskel er, at der ikke er behov for at generere html-sider til enhederne.

Output

«Uden at kende stedet, skal du ikke gå i vandet» Jeg mener, at jeg før jeg begyndte arbejdet burde have defineret målene og opgaverne mere klart og også dykket dybere ned i studiet af de nødvendige teknologier og metoder til deres implementering på forskellige kunder. Projektet er allerede tæt på at være færdigt, men jeg vender muligvis tilbage for at fortælle om, hvordan jeg igen fuckede tingene op. Jeg har lært meget i udviklingsprocessen, men der er endnu mere at lære fremover. Hvis du har læst dette langt, så tak for det.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster