Ứng dụng máy chủ hoặc chế tạo xe đạp nâng cao dựa trên khung C# .Net

Nhập

Mọi chuyện bắt đầu khi một đồng nghiệp đề nghị tôi tạo một dịch vụ web nhỏ. Nó được cho là giống như Tinder, nhưng dành cho đám đông CNTT. Chức năng cực kỳ đơn giản, bạn đăng ký, điền vào hồ sơ và chuyển sang mục chính, đó là tìm người để nói chuyện và mở rộng kết nối cũng như làm quen với những người mới.

Ở đây tôi phải rút lui và kể một chút về bản thân mình, để sau này có thể hiểu rõ hơn lý do tại sao tôi lại thực hiện những bước phát triển như vậy.

Hiện tại, tôi đang giữ vị trí Nghệ sĩ kỹ thuật trong một studio trò chơi, kinh nghiệm lập trình của tôi về C# chỉ được xây dựng dựa trên việc viết tập lệnh và tiện ích cho Unity, ngoài ra, còn tạo ra các plugin để làm việc ở cấp độ thấp với các thiết bị Android. Tôi chưa dám mạo hiểm vượt ra khỏi thế giới nhỏ bé này và rồi một cơ hội như vậy đã xuất hiện.

Phần 1. Tạo nguyên mẫu khung

Sau khi quyết định dịch vụ này sẽ như thế nào, tôi bắt đầu tìm kiếm các phương án để triển khai. Điều dễ dàng nhất là tìm ra một loại giải pháp làm sẵn nào đó, trên đó, giống như một con cú trên quả địa cầu, cơ chế của chúng ta có thể bị điều khiển và toàn bộ sự việc có thể bị công chúng chỉ trích.
Nhưng điều này chẳng thú vị chút nào, tôi không thấy bất kỳ thách thức hay ý nghĩa nào trong đó, và do đó tôi bắt đầu nghiên cứu các công nghệ web và phương pháp tương tác với chúng.

Tôi bắt đầu học bằng cách xem các bài báo và tài liệu về C# .Net. Ở đây tôi đã tìm thấy nhiều cách khác nhau để hoàn thành nhiệm vụ. Có nhiều cơ chế để tương tác với mạng, từ các giải pháp hoàn chỉnh như dịch vụ ASP.Net hoặc Azure, đến tương tác trực tiếp với các kết nối TcpHttp.

Sau lần thử đầu tiên với ASP, tôi ngay lập tức từ chối nó; theo tôi, đây là một quyết định quá khó khăn đối với dịch vụ của chúng tôi. Chúng tôi sẽ không sử dụng dù chỉ một phần ba khả năng của nền tảng này, vì vậy tôi tiếp tục tìm kiếm. Sự lựa chọn là giữa TCP và Http client-server. Ở đây, trên Habré, tôi tình cờ thấy một bài viết về máy chủ đa luồng, sau khi thu thập và thử nghiệm nó, tôi quyết định tập trung cụ thể vào việc tương tác với các kết nối TCP, vì lý do nào đó tôi nghĩ rằng http sẽ không cho phép tôi tạo một giải pháp đa nền tảng.

Phiên bản đầu tiên của máy chủ bao gồm xử lý kết nối, cung cấp nội dung trang web tĩnh và bao gồm cơ sở dữ liệu người dùng. Và để bắt đầu, tôi quyết định xây dựng chức năng làm việc với trang web để sau này tôi có thể thêm quá trình xử lý ứng dụng trên Android và iOS.

Đây là một số mã
Chuỗi chính nhận khách hàng trong một vòng lặp vô tận:

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

Bản thân trình xử lý khách hàng:

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à cơ sở dữ liệu đầu tiên được xây dựng trên SQL cục bộ:
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}");
}
}
}
}
}


Như bạn có thể thấy, phiên bản này khác rất ít so với phiên bản trong bài viết. Trên thực tế, ở đây chúng tôi chỉ thêm tính năng tải trang từ một thư mục trên máy tính và cơ sở dữ liệu (nhân tiện, tính năng này không hoạt động trong phiên bản này do kiến ​​​​trúc kết nối không chính xác).

Chương 2. Vít bánh xe

Sau khi kiểm tra máy chủ, tôi đi đến kết luận rằng đây sẽ là một giải pháp tuyệt vời(tiết lộ nội dung: không), cho dịch vụ của chúng tôi, vì vậy dự án bắt đầu có tính logic.
Từng bước một, các mô-đun mới bắt đầu xuất hiện và chức năng của máy chủ được mở rộng. Máy chủ đã có được một tên miền thử nghiệm và ssl mã hóa kết nối.

Thêm một chút mã mô tả logic của quá trình xử lý máy chủ và máy khách
Phiên bản cập nhật của máy chủ bao gồm việc sử dụng chứng chỉ.

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

Và cũng là trình xử lý ứng dụng khách mới có ủy quyền 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);
}

}
}


Nhưng vì máy chủ chỉ chạy trên kết nối TCP nên cần phải tạo một mô-đun có thể nhận dạng ngữ cảnh của yêu cầu. Tôi quyết định rằng ở đây một trình phân tích cú pháp sẽ phù hợp để chia yêu cầu từ khách hàng thành các phần riêng biệt mà tôi có thể tương tác để cung cấp cho khách hàng những câu trả lời cần thiết.

Trình phân tích cú pháp

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


Bản chất của nó là chia yêu cầu thành nhiều phần bằng cách sử dụng biểu thức chính quy. Chúng tôi nhận được tin nhắn từ khách hàng, chọn dòng đầu tiên chứa phương thức và url yêu cầu. Sau đó, chúng tôi đọc các tiêu đề mà chúng tôi đặt vào một mảng có dạng HeaderName=Content và chúng tôi cũng tìm thấy, nếu có, nội dung đi kèm (ví dụ: chuỗi truy vấn) mà chúng tôi cũng đặt vào một mảng tương tự. Ngoài ra, trình phân tích cú pháp còn tìm hiểu xem khách hàng hiện tại có được ủy quyền hay không và lưu trữ dữ liệu của anh ta. Tất cả các yêu cầu từ khách hàng được ủy quyền đều chứa hàm băm ủy quyền, được lưu trữ trong cookie, nhờ đó có thể tách logic hoạt động tiếp theo cho hai loại khách hàng và cung cấp cho họ câu trả lời chính xác.

Chà, một tính năng nhỏ, hay đáng để đưa vào một mô-đun riêng biệt, chuyển đổi các truy vấn như “site.com/@UserName” thành các trang người dùng được tạo động. Sau khi xử lý yêu cầu, các mô-đun sau sẽ hoạt động.

Chương 3. Lắp vô lăng, bôi trơn xích

Ngay sau khi trình phân tích cú pháp hoàn thành công việc của mình, trình xử lý sẽ hoạt động, đưa ra các hướng dẫn thêm cho máy chủ và chia quyền điều khiển thành hai phần.

Trình xử lý đơn giản

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

Trên thực tế, chỉ có một lần kiểm tra quyền của người dùng, sau đó quá trình xử lý yêu cầu bắt đầu.

Bộ điều khiển máy khách
Nếu người dùng không được ủy quyền thì chức năng của anh ta chỉ dựa trên việc hiển thị hồ sơ người dùng và cửa sổ đăng ký ủy quyền. Mã dành cho người dùng được ủy quyền trông gần giống nhau, vì vậy tôi thấy không có lý do gì để sao chép nó.

Người dùng trái phép

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à tất nhiên, người dùng phải nhận được một số loại nội dung trang, do đó, để phản hồi, có mô-đun sau chịu trách nhiệm phản hồi các yêu cầu tài nguyên.

Nhà vănBộ điều khiển

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


Nhưng để hiển thị cho người dùng hồ sơ của anh ấy và hồ sơ của những người dùng khác, tôi quyết định sử dụng RazorEngine, hay đúng hơn là một phần của nó. Nó cũng bao gồm việc xử lý các yêu cầu không hợp lệ và đưa ra mã lỗi thích hợp.

Dao cạoĐiều khiển

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à tất nhiên, để việc xác minh người dùng được ủy quyền hoạt động thì cần có sự ủy quyền. Mô-đun ủy quyền tương tác với cơ sở dữ liệu. Dữ liệu nhận được từ các biểu mẫu trên trang web được phân tích cú pháp khỏi ngữ cảnh, người dùng được lưu và đổi lại nhận được cookie cũng như quyền truy cập vào dịch vụ.

Mô-đun ủy quyền

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à đây là cách xử lý cơ sở dữ liệu:

Cơ sở dữ liệu

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à mọi thứ đều hoạt động như đồng hồ, công việc ủy ​​quyền và đăng ký, chức năng tối thiểu để truy cập dịch vụ đã có sẵn và đã đến lúc viết một ứng dụng và gắn kết toàn bộ mọi thứ lại với nhau bằng các chức năng chính mà mọi thứ đang được thực hiện.

Chương 4. Vứt xe đạp đi

Để giảm chi phí lao động khi viết hai ứng dụng cho hai nền tảng, tôi quyết định tạo một nền tảng chéo trên Xamarin.Forms. Một lần nữa, nhờ thực tế là nó có trong C#. Sau khi tạo một ứng dụng thử nghiệm chỉ gửi dữ liệu đến máy chủ, tôi phát hiện ra một điểm thú vị. Đối với một yêu cầu từ một thiết bị, để giải trí, tôi đã triển khai nó trên HttpClient và gửi nó đến máy chủ HttpRequestMessage, nơi chứa dữ liệu từ biểu mẫu ủy quyền ở định dạng json. Không đặc biệt mong đợi bất cứ điều gì, tôi mở nhật ký máy chủ và thấy có một yêu cầu từ thiết bị với tất cả dữ liệu. Một chút sững sờ, nhận thức về mọi việc đã làm trong 3 tuần qua trong một buổi tối uể oải. Để kiểm tra tính chính xác của dữ liệu đã gửi, tôi đã tập hợp một máy chủ thử nghiệm trên HttpListner. Sau khi nhận được một yêu cầu khác trên đó, tôi tách nó ra thành một vài dòng mã và nhận được dữ liệu KeyValuePair từ biểu mẫu. Phân tích cú pháp truy vấn đã được giảm xuống còn hai dòng.

Tôi đã bắt đầu thử nghiệm sâu hơn, nó chưa được đề cập trước đó, nhưng trên máy chủ trước đó, tôi cũng đã triển khai một cuộc trò chuyện được xây dựng trên websockets. Nó hoạt động khá tốt, nhưng chính nguyên tắc tương tác qua Tcp lại gây chán nản; phải thực hiện quá nhiều công việc không cần thiết để xây dựng thành thạo sự tương tác giữa hai người dùng bằng nhật ký trao đổi thư từ. Điều này bao gồm phân tích cú pháp yêu cầu chuyển đổi kết nối và thu thập phản hồi bằng giao thức RFC 6455. Do đó, trong máy chủ thử nghiệm, tôi quyết định tạo một kết nối websocket đơn giản. Chỉ để cho vui thôi.

Kết nối để trò chuyện

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

Va no đa hoạt động. Máy chủ tự cấu hình kết nối và tạo khóa phản hồi. Tôi thậm chí không phải cấu hình riêng đăng ký máy chủ qua SSL, chỉ cần hệ thống đã cài đặt chứng chỉ trên cổng được yêu cầu là đủ.

Về phía thiết bị và phía trang web, hai khách hàng đã trao đổi tin nhắn, tất cả điều này đã được ghi lại. Không có trình phân tích cú pháp khổng lồ nào làm chậm máy chủ, không yêu cầu điều này. Thời gian phản hồi đã giảm từ 200ms xuống còn 40-30ms. Và tôi đã đi đến quyết định đúng đắn duy nhất.

Ứng dụng máy chủ hoặc chế tạo xe đạp nâng cao dựa trên khung C# .Net

Loại bỏ việc triển khai máy chủ hiện tại trên Tcp và viết lại mọi thứ trong Http. Bây giờ dự án đang trong giai đoạn thiết kế lại, nhưng theo các nguyên tắc tương tác hoàn toàn khác. Hoạt động của thiết bị và trang web được đồng bộ hóa và gỡ lỗi và có một khái niệm chung, điểm khác biệt duy nhất là không cần tạo trang HTML cho thiết bị.

Đầu ra

“Nếu bạn không biết ford, đừng xuống nước” Tôi nghĩ rằng trước khi bắt đầu công việc, tôi nên xác định rõ ràng hơn các mục tiêu và mục tiêu cũng như đi sâu nghiên cứu các công nghệ và phương pháp cần thiết để triển khai chúng trên nhiều khách hàng khác nhau. Dự án đã gần hoàn thành, nhưng có lẽ tôi sẽ quay lại để nói về cách tôi đã lưu lại một số thứ. Tôi đã học được rất nhiều điều trong quá trình phát triển, nhưng thậm chí còn có nhiều điều để học hơn trong tương lai. Nếu bạn đã đọc đến đây, cảm ơn bạn đã làm như vậy.

Nguồn: www.habr.com

Mua dịch vụ lưu trữ đáng tin cậy cho các trang web có bảo vệ DDoS, máy chủ VPS VDS 🔥 Mua dịch vụ hosting website đáng tin cậy với bảo vệ DDoS, máy chủ VPS VDS | ProHoster