بناء الدراجة المتقدم أو تطبيق العميل / الخادم بناءً على إطار عمل C #

المقدمة



بدأ كل شيء عندما اقترح أحد الزملاء أن أقوم بإنشاء خدمة ويب صغيرة. كان من المفترض أن يكون شيئًا مثل tinder ، لكن لحشد تكنولوجيا المعلومات. الوظيفة بسيطة للغاية ، يمكنك التسجيل وملء ملف التعريف والانتقال إلى النقطة الرئيسية ، وهي العثور على محاور وتوسيع اتصالاتك والحصول على معارف جديدة.

هنا يجب أن أستطرد وأخبر قليلاً عن نفسي ، حتى يكون من الواضح في المستقبل لماذا اتخذت مثل هذه الخطوات في التنمية.



في الوقت الحالي ، أشغل منصب فنان تقني في استوديو ألعاب ، وكانت تجربتي في برمجة C # تعتمد فقط على كتابة البرامج النصية والأدوات المساعدة لـ Unity ، بالإضافة إلى إنشاء مكونات إضافية للعمل منخفض المستوى مع أجهزة Android. لم أخرج من هذا العالم الصغير بعد ، ثم ظهرت مثل هذه الفرصة.

الجزء 1. النماذج الأولية للإطار



بعد أن قررت كيف ستكون هذه الخدمة ، بدأت في البحث عن خيارات للتنفيذ. أسهل طريقة هي إيجاد نوع من الحلول الجاهزة ، والتي ، مثل البومة على الكرة الأرضية ، يمكن أن تسحبها ميكانيكينا وتضع كل شيء تحت الرقابة العامة.

لكن هذا ليس مثيرًا للاهتمام ، لم أر أي تحدٍ أو معنى في هذا ، وبالتالي بدأت بدراسة تقنيات الويب وطرق التفاعل معها.



لقد بدأت بالاطلاع على مقالات وتوثيق C #. وجدت هنا مجموعة متنوعة من الطرق لإنجاز المهمة. هناك العديد من الآليات للتفاعل مع الشبكة ، بدءًا من الحلول الكاملة مثل خدمات ASP.Net أو Azure ، إلى التفاعل المباشر مع اتصالات Tcp \ Http.



بعد إجراء المحاولة الأولى مع ASP ، رفضته على الفور ، في رأيي أنه كان قرارًا صعبًا للغاية بالنسبة لخدمتنا. لن نستخدم حتى ثلث إمكانيات هذه المنصة ، لذلك واصلت البحث. جاء الاختيار بين خادم العميل TCP و Http. هنا ، في Habré ، صادفت مقالًا عن خادم متعدد الخيوط ، بعد أن جمعته واختبرته ، قررت التركيز على التفاعل مع اتصالات 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("\r\n\r\n") >= 0 || Message.Length > 4096)
                {
                    Console.WriteLine(Message);
                    break;
                }
            }

            Match ReqMatch = Regex.Match(Message, @"^\w+\s+([^\s\?]+)[^\s]*\s+HTTP/.*|");
            if (ReqMatch == Match.Empty)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            string RequestUri = ReqMatch.Groups[1].Value;
            RequestUri = Uri.UnescapeDataString(RequestUri);
            if (RequestUri.IndexOf("..") >= 0)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            if (RequestUri.EndsWith("/"))
            {
                RequestUri += "index.html";
            }

            string FilePath = $"D:/Web/TestSite{RequestUri}";

            if (!File.Exists(FilePath))
            {
                ErrorWorker.SendError(Client, 404);
                return;
            }

            string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));

            string ContentType = "";

            switch (Extension)
            {
                case ".htm":
                case ".html":
                    ContentType = "text/html";
                    break;
                case ".css":
                    ContentType = "text/css";
                    break;
                case ".js":
                    ContentType = "text/javascript";
                    break;
                case ".jpg":
                    ContentType = "image/jpeg";
                    break;
                case ".jpeg":
                case ".png":
                case ".gif":
                    ContentType = $"image/{Extension.Substring(1)}";
                    break;
                default:
                    if (Extension.Length > 1)
                    {
                        ContentType = $"application/{Extension.Substring(1)}";
                    }
                    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 = $"HTTP/1.1 200 OK\nContent-Type: {ContentType}\nContent-Length: {FS.Length}\n\n";
            byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
            Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);

            while (FS.Position < FS.Length)
            {
                Count = FS.Read(Buffer, 0, Buffer.Length);
                Client.GetStream().Write(Buffer, 0, Count);
            }

            FS.Close();
            Client.Close();
        }
    }
}


local 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($"{item.login} {item.password}");
                }
            }
        }
    }
}


, , . ( , - ).



الفصل الثاني. شد العجلات



بعد اختبار تشغيل الخادم ، توصلت إلى استنتاج مفاده أن هذا سيكون حلاً رائعًا ( spoiler: no ) لخدمتنا ، لذلك بدأ المشروع في اكتساب المنطق.

خطوة بخطوة ، بدأت الوحدات النمطية الجديدة في الظهور وتوسعت وظائف الخادم. حصل الخادم على مجال اختبار وتشفير 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:\ssl\itinder.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" +
                    $"|{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($"\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 ، ونجد أيضًا ، إن وجد ، المحتوى المصاحب (على سبيل المثال ، سلسلة الاستعلام) ، والذي نقود أيضًا إلى مصفوفة مماثلة. بالإضافة إلى ذلك ، يكتشف المحلل اللغوي ما إذا كان العميل الحالي مرخصًا ويحفظ بياناته. تحتوي جميع الطلبات الواردة من العملاء المعتمدين على تجزئة تفويض ، يتم تخزينها في ملفات تعريف الارتباط ، بحيث يمكنك فصل منطق العمل الإضافي لنوعين من العملاء ومنحهم الإجابات الصحيحة.



حسنًا ، ميزة صغيرة وجميلة يجب إخراجها في وحدة منفصلة ، وتحويل الطلبات مثل "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 = $"HTTP/1.1 200 OK\nContent-Type: text/html";
                            WriteController.DefaultWriter(Header, FilePath);
                            break;
                        case "/profile":
                            RazorController.ProfileLoader(ViewPath);
                            break;
                        default:
//              site.com/page.html
                            if (!File.Exists(ViewPath + Context.RequestUrl) | block)
                            {
                                RazorController.ErrorLoader(404);
                               
                            }                            
                            else if (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
                            {
                                Header = WriteController.ContentType(Context.RequestUrl);
                                FilePath = ViewPath + Context.RequestUrl;
                                WriteController.DefaultWriter(Header, FilePath);
                            }                            
                            break;
                    }
                    break;

                case "POST":
                    AuthorizationController.MethodRecognizer();
                    break;

            }

        }

    }
}




, , , .



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 = $"{Header}\nContent-Length: {fileStream.Length}\n\n";
                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 OK\nContent-Type:";
            switch (extension)
            {
                case ".html":
                case ".htm":
                    return $"{Header} text/html";
                case ".css":
                    return $"{Header} text/css";
                case ".js":
                    return $"{Header} text/javascript";
                case ".jpg":
                case ".jpeg":
                case ".png":
                case ".gif":
                    return $"{Header} image/{extension}";
                default:
                    if (extension.Length > 1)
                    {
                        return $"{Header} application/" + extension.Substring(1);
                    }
                    else
                    {
                        return $"{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($"HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
                ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);


            }
            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($"{user.password} {user.password.Trim().Length}");
                DatabaseWorker.UserRegister(user);
                var response = Encoding.UTF8.GetBytes($"HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
                ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
            }
            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($"User{user.name} with id {user.uid} added");
                foreach (var item in users)
                {
                    Console.WriteLine(item.login + "\n");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            
        }

        public bool LoginValidate(string login)
        {
            if (users.Any(x => x.login.ToLower() == login.ToLower()))
            {
                Console.WriteLine("Login already exists");
                return false;
            }
            return true;
        }
        public void UserUpdate(User user)
        {
            var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
            UserToUpdate = user;
            DataBase.SubmitChanges();
            Console.WriteLine($"User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
            foreach (var item in users)
            {
                Console.WriteLine(item.login + "\n");
            }
        }
        public User CookieValidate(string CookieInput)
        {
            User user = null;
            try
            {
                user = users.SingleOrDefault(x => x.cookie == CookieInput);
            }
            catch
            {
                return null;
            }
            if (user != null) return user;
            else return null;
        }
        public User FindUser(string login)
        {
            User user = null;
            try
            {
                user = users.Single(x => x.login.ToLower() == login.ToLower());
                if (user != null)
                {
                    return user;
                }
                else
                {
                    return null;
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}





ويعمل كل شيء مثل آلية العمل ، والتفويض والتسجيل ، والحد الأدنى من وظائف الوصول إلى الخدمة متاح بالفعل ، وقد حان الوقت لكتابة تطبيق وربط كل شيء بالوظائف الرئيسية التي يتم من أجلها كل شيء.



الفصل 4. رمي الدراجة بعيدا



من أجل تقليل تكاليف العمالة لكتابة تطبيقين لمنصتين ، قررت إنشاء منصة مشتركة على Xamarin.Forms. مرة أخرى ، نظرًا لحقيقة أنه موجود في C #. بعد أن قدمت تطبيقًا اختباريًا يرسل البيانات ببساطة إلى الخادم ، واجهت نقطة واحدة مثيرة للاهتمام. لطلب من الجهاز ، من أجل الفائدة ، قمت بتطبيقه على HttpClient وألقيته على الخادم HttpRequestMessage ، والذي يحتوي على بيانات من نموذج التفويض بتنسيق json. دون توقع أي شيء ، فتحت سجل الخادم ورأيت طلبًا من الجهاز بجميع البيانات. ذهول طفيف ، إدراك كل شيء تم إنجازه في الأسابيع الثلاثة الأخيرة من أمسية ثقيلة. للتحقق من صحة البيانات المرسلة ، جمعت خادم اختبار على 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 مللي ثانية. وقد توصلت إلى القرار الصحيح الوحيد.



قم برمي تنفيذ الخادم الحالي إلى Tcp وأعد كتابة كل شيء ضمن Http. الآن المشروع في مرحلة إعادة التصميم ، ولكن بالفعل وفقًا لمبادئ تفاعل مختلفة تمامًا. تتم مزامنة تشغيل الأجهزة والموقع وتصحيح أخطائها ولديها مفهوم مشترك ، مع الاختلاف الوحيد الذي لا تحتاجه الأجهزة لإنشاء صفحات html.



انتاج |



"عدم معرفة فورد ، لا تطغى على رأسك في الماء" أعتقد ، قبل البدء في العمل ، كان يجب أن أحدد الأهداف والغايات بشكل أوضح ، وكذلك الخوض في دراسة التقنيات والأساليب اللازمة لتنفيذها على مختلف العملاء. المشروع على وشك الانتهاء بالفعل ، لكن ربما سأعود للحديث عن كيفية حصولي على بعض الأشياء مرة أخرى. لقد تعلمت الكثير أثناء عملية التطوير ، ولكن لا يزال هناك المزيد لنتعلمه في المستقبل. إذا كنت قد قرأت هذا الآن ، فأشكرك على ذلك.



All Articles