C# .Net çərçivəsinə əsaslanan təkmil velosiped və ya müştəri-server proqramı

Giriş

Hər şey bir həmkarımın kiçik bir veb xidməti yaratmağı təklif etməsi ilə başladı. Bu, zibil kimi bir şey olmalı idi, lakin İT görüşməsi üçün. Funksionallıq tamamilə sadədir, siz qeydiyyatdan keçərək profili doldurursunuz və əsas məqama keçirsiniz, yəni həmsöhbət tapmaq və əlaqələrinizi genişləndirmək və yeni tanışlıqlar etmək.

Burada mən kənara çəkilib bir az özüm haqqında danışmalıyam ki, gələcəkdə inkişafda niyə belə addımlar atdığım daha aydın olsun.

Hazırda oyun studiyasında Texniki Rəssam vəzifəsini tuturam, mənim C# proqramlaşdırma təcrübəm yalnız Unity üçün skriptlərin və utilitlərin yazılmasına və bundan əlavə, android cihazları ilə aşağı səviyyəli iş üçün plaginlərin yaradılmasına əsaslanırdı. Bu dünyadan kənarda, mən hələ belə bir fürsəti seçməmişəm və sonra ortaya qoymamışam.

Hissə 1. Çərçivənin Prototipləşdirilməsi

Bu xidmətin necə olacağına qərar verdikdən sonra həyata keçirmək üçün seçimlər axtarmağa başladım. Ən asan yol, qlobusdakı bayquş kimi mexanikamızı çəkə biləcəyiniz və hər şeyi ictimai qınaq üçün hazırlaya biləcəyiniz bir növ hazır həll tapmaq olardı.
Ancaq bu maraqlı deyil, mən bunda heç bir çətinlik və məna görmədim və buna görə də veb texnologiyalarını və onlarla qarşılıqlı əlaqə üsullarını öyrənməyə başladım.

Tədqiqat C # .Net-də məqalələrə və sənədlərə baxmaqla başladı. Burada tapşırığı yerinə yetirmək üçün müxtəlif yollar tapdım. ASP.Net və ya Azure xidmətləri kimi tam hüquqlu həllərdən TcpHttp əlaqələri ilə birbaşa qarşılıqlı əlaqəyə qədər şəbəkə ilə qarşılıqlı əlaqə üçün bir çox mexanizmlər mövcuddur.

ASP ilə ilk cəhddən sonra onu dərhal ləğv etdim, fikrimcə, xidmətimiz üçün çox çətin bir qərar idi. Bu platformanın imkanlarının üçdə birini belə istifadə etməyəcəyik, ona görə də axtarışımı davam etdirdim. Seçim TCP və Http müştəri-server arasında yarandı. Burada, Habré-də, haqqında bir məqalə ilə qarşılaşdım çox yivli server, toplayıb sınaqdan keçirdikdən sonra diqqətimi TCP əlaqələri ilə qarşılıqlı əlaqəyə yönəltmək qərarına gəldim, nədənsə http-nin platformalar arası həll yaratmağa imkan verməyəcəyini düşündüm.

Serverin ilk versiyası əlaqələri idarə etmək, statik veb səhifə məzmununa xidmət göstərmək və istifadəçi verilənlər bazası daxil olmaqla. Başlayanlar üçün mən saytla işləmək üçün bir funksional qurmaq qərarına gəldim ki, daha sonra Android və iOS-da tətbiqlərin işlənməsini buraya bağlaya bildim.

Budur bəzi kod
Müştəriləri sonsuz bir döngədə qəbul edən əsas mövzu:

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

Müştəri idarəçisinin özü:

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

Və yerli SQL üzərində qurulmuş ilk verilənlər bazası:

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

Gördüyünüz kimi, bu versiya məqalədəkindən az fərqlənir. Əslində, buraya yalnız kompüterdəki bir qovluqdan səhifələrin yüklənməsi və verilənlər bazası əlavə edildi (yeri gəlmişkən, səhv bağlantı arxitekturasına görə bu versiyada işləmədi).

Fəsil 2

Serveri sınaqdan keçirdikdən sonra bunun əla bir həll olacağı qənaətinə gəldim (spoyler: yox), xidmətimiz üçün layihə məntiq qazanmağa başladı.
Addım-addım yeni modullar görünməyə başladı və serverin funksionallığı artdı. Serverdə sınaq domeni və ssl əlaqə şifrələməsi var.

Serverin məntiqini və müştərilərin işlənməsini təsvir edən bir az daha çox kod
Sertifikatdan istifadə də daxil olmaqla serverin yenilənmiş versiyası.

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

Həmçinin ssl vasitəsilə avtorizasiyaya malik yeni müştəri işləyicisi:

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

}
}

Lakin server yalnız TCP bağlantısı üzərində işlədiyi üçün sorğu kontekstini tanıya bilən modul yaratmaq lazımdır. Qərara gəldim ki, burada müştəridən gələn sorğunu müştəriyə lazımi cavabları vermək üçün qarşılıqlı əlaqə qura biləcəyim ayrı hissələrə böləcək bir təhlilçi uyğun gəlir.

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

Onun mahiyyəti ondan ibarətdir ki, müntəzəm ifadələrin köməyi ilə tələbi hissələrə bölmək. Müştəridən bir mesaj alırıq, metodu və url sorğusunu ehtiva edən ilk sətri seçin. Sonra biz HeaderName = Content formasının massivinə daxil etdiyimiz başlıqları oxuyuruq və həmçinin, əgər varsa, oxşar massivə daxil etdiyimiz əlavə məzmunu (məsələn, sorğu sətri) tapırıq. Bundan əlavə, təhlilçi cari müştərinin səlahiyyətli olub olmadığını öyrənir və məlumatlarını saxlayır. Səlahiyyətli müştərilərin bütün sorğularında kukilərdə saxlanılan avtorizasiya hashı var, bunun sayəsində iki növ müştəri üçün sonrakı iş məntiqini ayırmaq və onlara düzgün cavablar vermək mümkündür.

Yaxşı, "site.com/@UserName" kimi sorğuları dinamik şəkildə yaradılan istifadəçi səhifələrinə çevirən, ayrıca modula köçürülməli olan kiçik, gözəl bir xüsusiyyət. Sorğu işləndikdən sonra aşağıdakı modullar işə düşür.

Fəsil 3. Sükanın quraşdırılması, zəncirin yağlanması

Parser başa çatdıqdan sonra idarəedici işə başlayır, serverə əlavə göstərişlər verir və idarəetməni iki hissəyə bölür.

sadə işləyici

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

Əslində, istifadəçi icazəsi üçün yalnız bir yoxlama var, ondan sonra sorğunun emalı başlayır.

Müştəri Nəzarətçiləri
Əgər istifadəçiyə icazə verilməyibsə, o zaman onun üçün funksionallıq yalnız istifadəçi profillərinin göstərilməsi və avtorizasiya qeydiyyatı pəncərəsinə əsaslanır. Səlahiyyətli istifadəçi üçün kod təxminən eyni görünür, ona görə də onu təkrarlamaq üçün heç bir səbəb görmürəm.

İcazəsiz istifadəçi

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;

}

}

}
}

Və əlbəttə ki, istifadəçi səhifələrin bəzi məzmununu almalıdır, buna görə də cavablar üçün resurs sorğusuna cavab vermək üçün cavabdeh olan aşağıdakı modul var.

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

Amma istifadəçiyə öz profilini və digər istifadəçilərin profillərini göstərmək üçün RazorEngine-dən, daha doğrusu, onun bir hissəsindən istifadə etmək qərarına gəldim. Buraya həmçinin etibarsız sorğuların işlənməsi və müvafiq səhv kodunun verilməsi daxildir.

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

Və əlbəttə ki, səlahiyyətli istifadəçilərin işləməsi üçün avtorizasiya lazımdır. Avtorizasiya modulu verilənlər bazası ilə qarşılıqlı əlaqədədir. Saytdakı formalardan alınan məlumatlar kontekstdən təhlil edilir, istifadəçi saxlanılır və bunun müqabilində kukilər və xidmətə giriş əldə edir.

Avtorizasiya modulu

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

Və verilənlər bazası belə görünür:

Verilənlər bazası

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


Və hər şey saat işi, avtorizasiya və qeydiyyat işi kimi işləyir, xidmətə girişin minimum funksionallığı artıq mövcuddur və ərizə yazmaq və hər şeyi hər şeyin edildiyi əsas funksiyalarla bağlamaq vaxtıdır.

4-cü fəsil

İki platforma üçün iki ərizə yazmaq üçün əmək xərclərini azaltmaq üçün Xamarin.Forms-da çarpaz platforma yaratmağa qərar verdim. Yenə də C#-da olması sayəsində. Sadəcə serverə məlumat göndərən bir sınaq proqramı hazırladıqdan sonra maraqlı bir məqamla qarşılaşdım. Cihazdan bir sorğu üçün, əylənmək üçün onu HttpClient-də tətbiq etdim və json formatında avtorizasiya formasından məlumatları ehtiva edən HttpRequestMessage serverinə atdım. Xüsusilə heç bir şey gözləmədən, server jurnalını açdım və oradakı bütün məlumatlarla cihazdan bir sorğu gördüm. Yüngül stupor, son 3 həftə ərzində edilən hər şeydən xəbərdar olmaq. Göndərilən məlumatların düzgünlüyünü yoxlamaq üçün HttpListner-də test serveri yığdım. Növbəti sorğunu artıq qəbul etdikdən sonra onu bir neçə kod sətirinə ayırdım, formadan KeyValuePair məlumatını aldım. Sorğunun təhlili iki sətirə endirildi.

Daha da sınaqdan keçirməyə başladım, əvvəllər qeyd olunmadı, amma əvvəlki serverdə hələ də websockets üzərində qurulmuş bir söhbət həyata keçirdim. Olduqca yaxşı işlədi, lakin Tcp vasitəsilə qarşılıqlı əlaqə prinsipi əsəbi idi, yazışmaların qeydiyyatı ilə iki istifadəçinin qarşılıqlı əlaqəsini düzgün qurmaq üçün çox əlavə istehsal edilməli idi. Bu, RFC 6455 protokolundan istifadə edərək əlaqənin dəyişdirilməsi sorğusunun təhlili və cavabın toplanması daxildir.Ona görə də test serverində sadə veb-soket bağlantısı yaratmağa qərar verdim. Sırf maraq naminə.

Çat bağlantısı

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

Və işlədi. Server özü əlaqə qurdu, cavab açarı yaratdı. Hətta ssl vasitəsilə server qeydiyyatını ayrıca konfiqurasiya etməli deyildim, sistemdə artıq tələb olunan portda sertifikat quraşdırılmış olması kifayətdir.

Cihaz tərəfində və sayt tərəfində iki müştəri mesaj mübadiləsi apardı, bütün bunlar qeyd edildi. Serveri yavaşlatan nəhəng təhlilçilər yoxdur, bunların heç biri tələb olunmur. Cavab müddəti 200 ms-dən 40-30 ms-ə endirildi. Və yeganə düzgün qərara gəldim.

C# .Net çərçivəsinə əsaslanan təkmil velosiped və ya müştəri-server proqramı

Tcp-də mövcud server tətbiqini atın və Http altında hər şeyi yenidən yazın. İndi layihə yenidən dizayn mərhələsindədir, lakin tamamilə fərqli qarşılıqlı fəaliyyət prinsiplərinə uyğun olaraq. Cihazların və saytın işləməsi sinxronlaşdırılır və sazlanır və ümumi konsepsiyaya malikdir, yeganə fərq, cihazların html səhifələri yaratmasına ehtiyac duymamasıdır.

Buraxılış

"Geri bilmədən başını suya soxma" Düşünürəm ki, işə başlamazdan əvvəl məqsəd və vəzifələri daha dəqiq müəyyən etməli, eləcə də müxtəlif müştərilərdə onların həyata keçirilməsi üçün lazımi texnologiyalar və metodların öyrənilməsinə başlamalıydım. Layihə artıq başa çatmaq üzrədir, amma ola bilsin ki, mən yenidən bəzi şeyləri necə pozduğum barədə danışmaq üçün qayıdacağam. İnkişaf prosesində çox şey öyrəndim, amma gələcəkdə öyrənməli olduğum daha çox şey var. Bura qədər oxumusunuzsa, oxuduğunuz üçün təşəkkür edirik.

Mənbə: www.habr.com