Txirrindularitza aurreratua edo bezero-zerbitzariko aplikazioa C# .Net framework-ean oinarrituta

Sarrera

Lankide batek web zerbitzu txiki bat egitea proposatu zidanean hasi zen dena. Tinder baten antzeko zerbait izan behar zen, baina IT hangout baterako. Funtzionalitatea guztiz erraza da, erregistratu, profil bat bete eta puntu nagusira igarotzen zara, hau da, solaskide bat aurkitzea eta zure konexioak zabaltzea eta ezagun berriak egitea.

Hemen digresioa egin behar dut eta nire buruari buruz pixka bat kontatu, etorkizunean argiago gera dadin zergatik eman ditudan pauso horiek garapenean.

Momentu honetan Artista Tekniko baten postua daukadan joko estudio batean, nire C# programazio esperientzia Unity-rako scriptak eta utilitateak idaztean oinarritzen zen eta, horretaz gain, Android gailuekin maila baxuko lanerako pluginak sortzean. Mundu txiki honetatik kanpo, oraindik ez dut aukera hori aukeratu eta gero piztu.

1. zatia. Markoen prototipatzea

Zerbitzu hau nolakoa izango den erabakita, ezartzeko aukerak bilatzen hasi nintzen. Modurik errazena prest egindako irtenbideren bat aurkitzea izango litzateke, zeinetan, hontza mundu batean bezala, gure mekanika atera eta guztia zentsura publikorako jarri dezakezu.
Baina hau ez da interesgarria, ez nion erronka eta zentzurik ikusten honi, eta, horregatik, web teknologiak eta haiekin elkarreragiteko metodoak aztertzen hasi nintzen.

Ikerketa C # .Net-en artikuluak eta dokumentazioa ikusiz hasi zen. Hemen zeregina betetzeko hainbat modu aurkitu ditut. Sarearekin elkarreragiteko mekanismo asko daude, ASP.Net edo Azure zerbitzuak bezalako soluzio osoetatik hasi eta TcpHttp konexioekin zuzeneko interakziora arte.

ASPrekin lehen saiakera eginda, berehala bertan behera utzi nuen, nire ustez gure zerbitzurako erabaki zaila zen. Plataforma honen gaitasunen herena ere ez dugu erabiliko, beraz, bilaketarekin jarraitu nuen. TCP eta Http bezero-zerbitzariaren artean sortu zen aukera. Hemen, HabrΓ©-n, honi buruzko artikulu bat topatu dut hari anitzeko zerbitzaria, hori bildu eta probatu ondoren, TCP konexioekin elkarreraginean zentratzea erabaki nuen, arrazoiren batengatik pentsatu nuen http-k ez zidala plataforma anitzeko soluziorik sortzen utziko.

Zerbitzariaren lehen bertsioak konexioak kudeatzea, web-orrietako eduki estatikoak hornitzea eta erabiltzaileen datu-basea barne hartzen zituen. Eta hasteko, gunearekin lan egiteko funtzional bat eraikitzea erabaki nuen, geroago Android eta ios-en aplikazioen prozesamendua hemen lotu ahal izateko.

Hona hemen kode batzuk
Bezeroak begizta amaigabean onartzen dituen hari nagusia:

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

Bezeroaren kudeatzailea bera:

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

Eta SQL lokalean eraikitako lehen datu-basea:

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

Ikus dezakezunez, bertsio hau artikulukoarekin ez da gutxi. Izan ere, hemen ordenagailuko karpeta bateko orrialdeak eta datu-basea kargatzea baino ez zen gehitu (horrek, bide batez, bertsio honetan ez zuen funtzionatu, konexio-arkitektura okerra zelako).

2. kapitulua

Zerbitzaria probatu ondoren, hau irtenbide bikaina izango zela ondorioztatu nuen (spoiler: ez), gure zerbitzurako, proiektua logika hartzen hasi zen.
Pausoz pauso, modulu berriak agertzen hasi ziren eta zerbitzariaren funtzionaltasuna hazten joan zen. Zerbitzariak proba-domeinua eta ssl konexio-enkriptatzea ditu.

Kode apur bat gehiago zerbitzariaren logika eta bezeroen prozesamendua deskribatzen dituena
Zerbitzariaren bertsio eguneratua, ziurtagiri baten erabilera barne.

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

Baita ssl bidez baimendutako bezero-kudeatzaile berri bat ere:

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

}
}

Baina zerbitzariak TCP konexio batean soilik lan egiten duenez, beharrezkoa da eskaeraren testuingurua ezagutu dezakeen modulu bat sortzea. Erabaki nuen hemen analizatzaile bat egokia dela, bezeroaren eskaera zati ezberdinetan banatuko duena, eta horiekin elkarreragin dezakedan bezeroari beharrezko erantzunak emateko.

analizatzailea

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

Bere funtsa esamolde erregularren laguntzaz eskaera zatitan zatitzea datza. Bezeroaren mezu bat jasotzen dugu, hautatu metodoa eta eskaera url-a duen lehen lerroa. Ondoren, goiburuak irakurtzen ditugu, HeaderName = Content formako array batean gidatzen ditugunak, eta hala badagokio, erantsitako edukia ere aurkitzen dugu (adibidez, kontsulta-katea) antzeko array batera gidatzen duguna. Horrez gain, analizatzaileak uneko bezeroa baimenduta dagoen ala ez jakiten du eta bere datuak gordetzen ditu. Baimendutako bezeroen eskaera guztiek baimen-hash bat dute, cookieetan gordetzen dena, eta horri esker, bi bezero motaren lan-logika gehiago bereiztea eta erantzun zuzenak ematea posible da.

Beno, aparteko modulu batera eraman behar den funtzio txiki eta polita, "site.com/@UserName" bezalako eskaerak dinamikoki sortutako erabiltzaile-orri bihurtuz. Eskaera prozesatu ondoren, hurrengo moduluak sartzen dira jokoan.

3. kapitulua. Eskulekua instalatzea, katea lubrifikatzea

Analizatzailea amaitu bezain laster, kudeatzailea jokoan sartzen da, zerbitzariari argibide gehiago emanez eta kontrola bi zatitan banatuz.

kudeatzaile sinplea

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

Izan ere, erabiltzailearen baimenaren egiaztapen bakarra dago, eta horren ostean hasten da eskaera prozesatzen.

Bezeroen Kontrolatzaileak
Erabiltzailea baimenduta ez badago, berarentzat funtzionalitatea erabiltzailearen profilak eta baimena erregistratzeko leihoan soilik oinarritzen da. Baimendutako erabiltzaile baten kodeak itxura bera du, beraz, ez dut ikusten bikoizteko arrazoirik.

Baimenik gabeko erabiltzailea

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;

}

}

}
}

Eta noski, erabiltzaileak orrialdeetako eduki batzuk jaso behar ditu, beraz, erantzunetarako hurrengo modulua dago, baliabideen eskaera bati erantzuteaz arduratzen dena.

WriterController

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

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

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

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

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


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


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


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


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


quot;{Header} application/" + extension.Substring(1);
}
else
{
return


quot;{Header} application/unknown";
}
}
}

public void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}

public void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}

Baina erabiltzaileari bere profila eta beste erabiltzaile batzuen profilak erakusteko, RazorEngine erabiltzea erabaki nuen, edo hobeto esanda, zati bat. Eskaera txarrak kudeatzea eta errore-kode egokia igortzea ere barne hartzen du.

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

Eta noski, baimendutako erabiltzaileen egiaztapenak funtziona dezan, baimena behar da. Baimen moduluak datu-basearekin elkarreragiten du. Webguneko inprimakietatik jasotako datuak testuingurutik aztertzen dira, erabiltzailea gordetzen da eta cookieak eta zerbitzurako sarbidea jasotzen ditu horren truke.

Baimen modulua

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

Eta hau da datu-basearen itxura:

Datu-basea

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


Eta dena erlojupeko moduan funtzionatzen du, baimendu eta erregistratzeko lanak, zerbitzurako sarbidea izateko gutxieneko funtzionaltasuna eskuragarri dago jada eta aplikazio bat idazteko eta dena egiten den funtzio nagusiekin lotzeko garaia da.

4. kapitulua

Bi plataformatarako bi aplikazio idazteko lan-kostuak murrizteko, Xamarin.Forms-en plataforma gurutzatua egitea erabaki nuen. Berriz ere, C#-n egoteari esker. Zerbitzariari datuak bidaltzen dizkion proba aplikazio bat egin ondoren, momentu interesgarri batekin egin nuen topo. Gailuaren eskaera bat egiteko, dibertitzeko, HttpClient-en inplementatu nuen eta Json formatuan baimen-formularioko datuak dituen HttpRequestMessage zerbitzarira bota nuen. Bereziki ezer espero gabe, zerbitzariaren erregistroa ireki eta gailutik eskaera bat ikusi nuen han datu guztiekin. Zoragarritasun arina, azken 3 asteetan iluntzean egindako guztiaren kontzientzia. Bidalitako datuen zuzentasuna egiaztatzeko, proba-zerbitzari bat muntatu nuen HttpListner-en. Dagoeneko hurrengo eskaera jasota, kode lerro pare batean desmuntatu nuen, KeyValuePair datuak formulariotik lortu. Kontsulten analisia bi lerrotara murriztu da.

Gehiago probatzen hasi nintzen, lehenago ez zen aipatu, baina aurreko zerbitzarian oraindik websocketetan eraikitako txat bat ezarri nuen. Nahiko ondo funtzionatu zuen, baina Tcp bidezko interakzio printzipioa oso etsigarria zen, gehigarri gehiegi sortu behar izan zen korrespondentziaren erregistroarekin bi erabiltzaileren elkarrekintza zuzen eraikitzeko. Honek konexioa aldatzeko eskaera bat analizatzea eta RFC 6455 protokoloa erabiliz erantzuna biltzea barne hartzen ditu. Horregatik, probako zerbitzarian, websocket konexio sinple bat sortzea erabaki nuen. Interes hutsagatik.

Txat konexioa

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

Eta funtzionatu zuen. Zerbitzariak berak ezarri zuen konexioa, erantzun-gako bat sortu zuen. Ez nuen zerbitzariaren erregistroa ssl bidez bereizita konfiguratu beharrik izan, nahikoa da sistemak behar den portuan instalatuta duela ziurtagiria.

Gailuaren aldetik eta gunearen aldetik, bi bezerok mezuak trukatu zituzten, hau guztia erregistratuta zegoen. Zerbitzaria moteltzen duen analizatzaile handirik ez, ez zen hauetako bat behar. Erantzun denbora 200 ms-tik 40-30 ms-ra murriztu da. Eta erabaki zuzen bakarra hartu nuen.

Txirrindularitza aurreratua edo bezero-zerbitzariko aplikazioa C# .Net framework-ean oinarrituta

Bota ezazu uneko zerbitzariaren inplementazioa Tcp-en eta berridatzi dena Http-en. Orain proiektua birdiseinuaren fasean dago, baina elkarrekintza-printzipio guztiz ezberdinen arabera. Gailuen eta gunearen funtzionamendua sinkronizatuta eta arazketatuta dago eta kontzeptu komun bat du, gailuek html orriak sortu behar ez dituztelako desberdintasun bakarrarekin.

Irteera

"Gada ezagutu gabe, ez sartu burua uretara" Uste dut, lanean hasi aurretik, helburuak eta helburuak argiago zehaztu beharko nituzkeela, baita hainbat bezerotan ezartzeko beharrezkoak diren teknologia eta metodoen azterketan sakondu ere. Proiektua amaitzear dago jada, baina agian berriro itzuliko naiz zenbait gauza berriro nola izorratu ditudan hitz egitera. Asko ikasi nuen garapen prozesuan, baina etorkizunean ikasteko gehiago dago. Honaino irakurri baduzu, eskerrik asko irakurtzeagatik.

Iturria: www.habr.com