C# .Net рамкасынын негизинде өркүндөтүлгөн велосипед куруу же кардар-сервер колдонмосу

кирүү

Мунун баары бир кесиптешим мага кичинекей веб-сервис түзүүнү сунуш кылганда башталды. Бул Tinder сыяктуу бир нерсе болушу керек болчу, бирок IT эл үчүн. Функционалдык өтө жөнөкөй, сиз катталасыз, профилди толтурасыз жана негизги нерсеге өтөсүз, тактап айтканда, сүйлөшүү үчүн адамды табуу жана байланыштарыңызды кеңейтүү жана жаңы таанышуулар.

Бул жерде мен артка чегинип, өзүм жөнүндө бир аз айтып беришим керек, келечекте мен эмне үчүн өнүгүүдө мындай кадамдарга барганым ачык-айкын болушу үчүн.

Учурда мен бир оюн студиясында Техникалык сүрөтчү кызматын ээлейм, менин C# тилиндеги программалоо тажрыйбам Unity үчүн скрипттерди жана утилиталарды жазууга гана курулган жана буга кошумча, Android түзмөктөрү менен төмөн деңгээлдеги иштөө үчүн плагиндерди түзүүгө негизделген. Мен бул кичинекей дүйнөнүн ары жагына чыга элек болчумун, анан ушундай мүмкүнчүлүк пайда болду.

1-бөлүк. Кадрларды прототиптөө

Бул кызмат кандай болорун чечип, мен ишке ашыруунун варианттарын издей баштадым. Эң эле оңой нерсе, кандайдыр бир даяр чечимди табуу, анын үстүнө, жер шарындагы үкүдөй, биздин механика тартылып, элдин сынына дуушар болот.
Бирок бул кызык эмес, мен андан эч кандай кыйынчылыкты же маанини көргөн жокмун, ошондуктан мен веб-технологияларды жана алар менен иштешүүнүн ыкмаларын үйрөнө баштадым.

Мен C# .Net боюнча макалаларды жана документтерди карап окуп баштадым. Бул жерде мен тапшырманы аткаруунун ар кандай жолдорун таптым. ASP.Net же Azure кызматтары сыяктуу толук кандуу чечимдерден TcpHttp байланыштары менен түз өз ара аракеттенүүгө чейин тармак менен өз ара аракеттенүүнүн көптөгөн механизмдери бар.

ASP менен биринчи аракетимди жасагандан кийин, мен андан дароо баш тарттым; менин оюмча, бул биздин кызмат үчүн өтө оор чечим болду. Биз бул платформанын мүмкүнчүлүктөрүнүн үчтөн бирин да колдонбойбуз, ошондуктан мен издөөмдү уланттым. Тандоо TCP жана Http кардар-серверинин ортосунда болгон. Бул жерде, Хабреде мен жөнүндө макалага туш болдум көп агымдуу сервер, аны чогултуп, сынап көрүп, мен TCP байланыштары менен өз ара аракеттенүүгө өзгөчө көңүл бурууну чечтим, эмнегедир мен http платформалар аралык чечимди түзүүгө жол бербейт деп ойлогом.

Сервердин биринчи версиясы туташууну иштетүүнү камтыган, веб-баракчанын статикалык мазмунун тейлеген жана колдонуучунун маалымат базасын камтыган. Баштоо үчүн, мен кийинчерээк Android жана iOS системаларында тиркемени иштетүүнү кошо алгыдай кылып, сайт менен иштөө үчүн функцияларды түзүүнү чечтим.

Бул жерде кээ бир код
Кардарларды чексиз циклде кабыл алган негизги жип:

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

Кардар иштетүүчү өзү:

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


Ал эми жергиликтүү SQLде курулган биринчи маалымат базасы:
using System;
using System.Data.Linq;
namespace ClearServer
{
    class DatabaseWorker
    {

        private static DatabaseWorker instance;

        public static DatabaseWorker GetInstance
        {
            get
            {
                if (instance == null)
                    instance = new DatabaseWorker();
                return instance;
            }
        }


        private DatabaseWorker()
        {
            string connectionStr = databasePath;
            using (DataContext db = new DataContext(connectionStr))
            {
                Table<User> users = db.GetTable<User>();
                foreach (var item in users)
                {
                    Console.WriteLine(

quot;{item.login} {item.password}");
}
}
}
}
}


Көрүнүп тургандай, бул версия макаладагыдан бир аз айырмаланат. Чынында, бул жерде биз жөн гана компьютердеги папкадан жана маалымат базасынан барактарды жүктөөнү коштук (айтмакчы, бул версияда туура эмес туташуу архитектурасына байланыштуу иштебей калган).

2-глава. Дөңгөлөктөрдү буроо

Серверди сынап көргөндөн кийин, мен бул эң сонун чечим болот деген жыйынтыкка келдим(спойлер: жок), биздин кызмат үчүн, ошондуктан долбоор логикага ээ боло баштады.
Акырындык менен жаңы модулдар пайда боло баштады жана сервердин функциялары кеңейди. Сервер тесттик доменди алды жана SSL байланышты шифрлөө.

Сервердин жана кардарды иштетүүнүн логикасын сүрөттөгөн бир аз көбүрөөк код
Сервердин жаңыртылган версиясы, ал сертификатты колдонууну камтыйт.

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

Ошондой эле SSL уруксаты менен жаңы кардар иштеткич:

using ClearServer.Core.Requester;
using System;
using System.Net.Security;
using System.Net.Sockets;

namespace ClearServer
{
    public class Client
    {
        public Client(TcpClient Client)
        {
            SslStream SSlClientStream = new SslStream(Client.GetStream(), false);
            try
            {
                SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "---------------------------------------------------------------------n" +

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

}
}


Бирок сервер TCP туташуусу боюнча гана иштегендиктен, суроо-талаптын контекстин тааный турган модулду түзүү керек. Мен бул жерде кардардын суроо-талабын өзүнчө бөлүктөргө бөлүп, кардарга керектүү жоопторду берүү үчүн өз ара аракеттене турган талдоочу ылайыктуу деп чечтим.

талдоочу

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


Анын маңызы суроо-талапты туруктуу сөз айкаштарын колдонуу менен бөлүктөргө бөлүүдө турат. Биз кардардан кабар алабыз, биринчи сапты тандайбыз, анда ыкма жана суроо url камтылган. Андан кийин биз HeaderName=Content формасындагы массивге киргизген рубрикаларды окуйбуз жана ошондой эле, эгерде бар болсо, коштомо мазмунду (мисалы, querystring) табабыз, аларды да окшош массивге киргизебиз. Мындан тышкары, талдоочу учурдагы кардар ыйгарым укуктуу экенин билип, анын маалыматтарын сактайт. Ыйгарым укуктуу кардарлардын бардык суроо-талаптары кукилерде сакталган авторизациялык хэшти камтыйт, мунун аркасында кардарлардын эки түрү үчүн андан аркы операциялык логиканы бөлүп, аларга туура жоопторду берүүгө болот.

Ооба, өзүнчө модулга коюуга арзырлык кичинекей, жакшы функция, "site.com/@UserName" сыяктуу суроо-талаптарды динамикалык түрдө түзүлгөн колдонуучу баракчаларына айландыруу. Сурамды иштеп чыккандан кийин төмөнкү модулдар ишке кирет.

3-глава. Рульду орнотуу, чынжырды майлоо

Талдоочу өз ишин бүтөөрү менен, иштетүүчү серверге кошумча көрсөтмөлөрдү берип, башкарууну эки бөлүккө бөлөт.

Жөнөкөй иштеткич

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

Чынында, колдонуучунун авторизациясы үчүн бир гана текшерүү бар, андан кийин суроо-талапты иштетүү башталат.

Кардар контроллерлору
Эгерде колдонуучуга уруксат берилбесе, анда ал үчүн функция колдонуучу профилдерин көрсөтүүгө жана авторизацияны каттоо терезесине гана негизделген. Ыйгарым укуктуу колдонуучунун коду болжол менен бирдей көрүнөт, андыктан аны кайталоого эч кандай себеп жок.

Уруксатсыз колдонуучу

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;

}

}

}
}


Анан, албетте, колдонуучу кандайдыр бир баракчанын мазмунун алышы керек, андыктан жооптор үчүн ресурстук суроо-талаптарга жооп берүү үчүн жооп берген төмөнкү модул бар.

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


Бирок колдонуучуга анын профилин жана башка колдонуучулардын профилдерин көрсөтүү үчүн мен RazorEngine, тагыраак айтканда, анын бир бөлүгүн колдонууну чечтим. Ал ошондой эле жараксыз суроо-талаптарды иштеп чыгууну жана тиешелүү ката кодун чыгарууну камтыйт.

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

Жана, албетте, ыйгарым укуктуу колдонуучулардын иштеши үчүн, авторизация керек. Авторизациялоо модулу маалымат базасы менен өз ара аракеттенет. Сайттагы формалардан алынган маалыматтар контексттен талданат, колдонуучу сакталат жана анын ордуна кукилерди жана кызматка кирүү мүмкүнчүлүгүн алат.

Авторизация модулу

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


Жана бул маалымат базасын иштетүү кандай көрүнөт:

Маалымат базасы

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


Жана бардыгы саат механизми, авторизация жана каттоо иштери сыяктуу иштейт, кызматка кирүү үчүн минималдуу функциялар бар жана арыз жазууга жана бардыгы аткарылып жаткан негизги функциялар менен бүт нерсени бириктирүүгө убакыт келди.

4-глава. Велосипедди ыргытуу

Эки платформа үчүн эки арыз жазууга эмгек чыгымдарын азайтуу үчүн, мен Xamarin.Forms боюнча кайчылаш платформа жасоону чечтим. Дагы бир жолу, ал C# тилинде болгонуна рахмат. Жөн гана серверге маалыматтарды жөнөтүүчү тесттик тиркемени жасап, мен кызыктуу жагдайга туш болдум. Түзмөктөн келген суроо үчүн, көңүл ачуу үчүн мен аны HttpClient'те ишке ашырдым жана аны json форматындагы авторизация формасынан маалыматтарды камтыган HttpRequestMessage серверине жөнөттүм. Өзгөчө эч нерсе күтпөстөн, мен сервер журналын ачтым жана ал жерде бардык маалыматтар менен аппараттан суроо-талапты көрдүм. Бир аз эс-учун жоготуп, акыркы 3 жуманын ичинде жасалган нерселердин баарын түшүнүү. Жөнөтүлгөн маалыматтардын тууралыгын текшерүү үчүн мен HttpListnerде тесттик серверди чогулттум. Буга чейин дагы бир суроону алгандан кийин, мен аны бир нече код саптарына бөлүп алдым жана формадан KeyValuePair маалыматын алдым. Суроолорду талдоо эки сапка кыскартылды.

Мен андан ары сынай баштадым, бул мурун айтылган эмес, бирок мурунку серверде мен вебсокеттерге курулган чатты да ишке ашырдым. Бул абдан жакшы иштеди, бирок Tcp аркылуу өз ара аракеттенүү принцибинин өзү көңүлдү чөгөрдү; эки колдонуучунун кат алышуу журналы менен өз ара аракеттенүүсүн компетенттүү куруу үчүн өтө көп керексиз иштерди жасоо керек болчу. Буга RFC 6455 протоколунун жардамы менен туташууну которуу өтүнүчүн талдоо жана жоопту чогултуу кирет.Ошондуктан, тест серверинде мен жөнөкөй вебсокет байланышын түзүүнү чечтим. Жөн гана көңүл ачуу үчүн.

Маекке туташуу

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

Жана ал иштеген. Сервер өзү байланышты конфигурациялап, жооп ачкычын түздү. Мага SSL аркылуу сервердин каттоосун өзүнчө конфигурациялоонун кереги жок болчу; бул системада талап кылынган портто орнотулган сертификат болгону жетиштүү.

Түзмөк тарабында жана сайт тарабында эки кардар билдирүүлөр менен алмашышты, мунун баары катталган. Серверди жайлатуучу эч кандай чоң талдоочулар, булардын бири да талап кылынган эмес. Жооп берүү убактысы 200 мсден 40-30 мсге чейин кыскарды. Ошондо мен бир гана туура чечимге келдим.

C# .Net рамкасынын негизинде өркүндөтүлгөн велосипед куруу же кардар-сервер колдонмосу

Tcp боюнча учурдагы сервердин ишке ашырылышын ыргытып, Http астында баарын кайра жазыңыз. Азыр долбоор кайра иштеп чыгуу стадиясында турат, бирок өз ара аракеттенүүнүн такыр башка принциптерине ылайык. Түзмөктөрдүн жана сайттын иштеши синхрондоштуруу жана мүчүлүштүктөрдү оңдоо жана жалпы концепцияга ээ, бир гана айырмасы, түзмөктөр үчүн HTML баракчаларын түзүүнүн кереги жок.

жыйынтыктоо

"Эгер өтмөктү билбесең, сууга түшпө" Мен жумушка киришерден мурун, мен дагы так аныкталган максаттарды жана милдеттерди, ошондой эле ар кандай кардарларга аларды ишке ашыруу үчүн зарыл технологияларды жана ыкмаларын изилдөөгө киришүү керек деп ойлойм. Долбоор азыр аяктоо алдында турат, бирок, балким, мен кээ бир нерселерди кантип сактап калганым жөнүндө сүйлөшүү үчүн кайра келем. Өнүгүү процессинде мен көп нерсени үйрөндүм, бирок келечекте үйрөнө турган дагы көп нерселер бар. Эгер сиз буга чейин окуган болсоңуз, муну кылганыңыз үчүн рахмат.

Source: www.habr.com

DDoS коргоосу, VPS VDS серверлери бар сайттар үчүн ишенимдүү хостинг сатып алыңыз 🔥 DDoS коргоосу, VPS VDS серверлери бар ишенимдүү веб-сайт хостингин сатып алыңыз | ProHoster