Altnivela biciklado aŭ kliento-servila aplikaĵo bazita sur C# .Net kadro

eniro

Ĉio komenciĝis kiam kolego sugestis, ke mi faru malgrandan retservon. Ĝi laŭsupoze estis io kiel tindro, sed por IT-hangout. La funkcieco estas tute simpla, vi registriĝas, plenigas profilon kaj transiras al la ĉefa punkto, nome trovi interparolanton kaj vastigi viajn rilatojn kaj fari novajn konatojn.

Ĉi tie mi devas malproksimiĝi kaj iom rakonti pri mi mem, por ke estonte estu pli klare, kial mi faris tiajn paŝojn en evoluo.

Nuntempe mi okupas la pozicion de Teknika Artisto en ludstudio, mia C#-programa sperto baziĝis nur sur verkado de skriptoj kaj utilecoj por Unity kaj, krom tio, kreado de kromprogramoj por malaltnivela laboro kun androidaj aparatoj. Ekster ĉi tiu eta mondo, mi ankoraŭ ne elektis kaj poste montris tian ŝancon.

Parto 1. Frame Prototyping

Decidinte, kia estos ĉi tiu servo, mi komencis serĉi eblojn por efektivigo. La plej facila maniero estus trovi ian pretan solvon, sur kiu, kiel strigo sur globo, vi povas tiri niajn mekanikistojn kaj elmeti la tuton por publika cenzuro.
Sed ĉi tio ne estas interesa, mi ne vidis ajnan defion kaj sencon en ĉi tio, kaj tial mi komencis studi retajn teknologiojn kaj metodojn por interagi kun ili.

La studo komenciĝis per rigardado de artikoloj kaj dokumentado en C# .Net. Ĉi tie mi trovis diversajn manierojn por plenumi la taskon. Estas multaj mekanismoj por interagi kun la reto, de plenrajtaj solvoj kiel ASP.Net aŭ Azure-servoj, ĝis rekta interago kun TcpHttp-konektoj.

Farinte la unuan provon kun ASP, mi tuj nuligis ĝin, laŭ mi estis tro malfacila decido por nia servo. Ni ne uzos eĉ trionon de la kapabloj de ĉi tiu platformo, do mi daŭrigis mian serĉon. La elekto ekestis inter TCP kaj Http kliento-servilo. Ĉi tie, ĉe Habré, mi trovis artikolon pri plurfadena servilo, kolektinte kaj testinte tion, mi decidis koncentriĝi pri interagado kun TCP-konektoj, ial mi pensis, ke http ne permesos al mi krei multiplatforman solvon.

La unua versio de la servilo inkludis pritrakti ligojn, servante supren senmovan retpaĝan enhavon, kaj inkluzive de uzantdatumbazo. Kaj por komenci, mi decidis konstrui funkcian por labori kun la retejo, por ke poste mi povu ligi la aplikaĵon pri Android kaj io ĉi tie.

Jen iu kodo
La ĉefa fadeno akceptanta klientojn en senfina buklo:

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

La klienttraktilo mem:

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

Kaj la unua datumbazo konstruita sur loka 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}");
}
}
}
}
}

Kiel vi povas vidi, ĉi tiu versio malmulte diferencas de tiu en la artikolo. Fakte, ĉi tie aldoniĝis nur la ŝarĝo de paĝoj el dosierujo en la komputilo kaj la datumbazo (kiu, cetere, ne funkciis en ĉi tiu versio, pro la malĝusta arkitekturo de konekto).

Ĉapitro 2

Post testi la servilon, mi alvenis al la konkludo, ke ĉi tio estus bonega solvo (spoiler: ne), por nia servo, do la projekto ekhavis logikon.
Paŝo post paŝo, novaj moduloj komencis aperi kaj la funkcieco de la servilo kreskis. La servilo havas provan domajnon kaj ssl-konektan ĉifradon.

Iom pli da kodo priskribanta la logikon de la servilo kaj la prilaborado de klientoj
Ĝisdatigita versio de la servilo, inkluzive de la uzo de atestilo.

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

Same kiel nova klienttraktilo kun rajtigo per 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);
}

}
}

Sed ĉar la servilo funkcias ekskluzive sur TCP-konekto, necesas krei modulon, kiu povus rekoni la petan kuntekston. Mi decidis, ke ĉi tie taŭgas analizilo, kiu rompos la peton de la kliento en apartajn partojn, kun kiuj mi povas interagi por doni al la kliento la necesajn respondojn.

Analizilo

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

Ĝia esenco kuŝas en tio, ke kun la helpo de regulaj esprimoj rompi la peton en partojn. Ni ricevas mesaĝon de la kliento, elektu la unuan linion, kiu enhavas la metodon kaj peton url. Poste ni legas la kapliniojn, kiujn ni kondukas en tabelon de la formo HeaderName = Content, kaj ankaŭ trovas, se ekzistas, la akompanan enhavon (ekzemple, querystring) kiun ni ankaŭ kondukas en similan tabelon. Krome, la analizanto ekscias ĉu la nuna kliento estas rajtigita kaj konservas siajn datumojn. Ĉiuj petoj de rajtigitaj klientoj enhavas rajtigan hash, kiu estas konservita en kuketoj, dank'al kiu eblas apartigi pluan laborlogikon por du specoj de klientoj kaj doni al ili la ĝustajn respondojn.

Nu, malgranda, bela funkcio, kiu devus esti movita en apartan modulon, konvertante petojn kiel "site.com/@UserName" en dinamike generitajn uzantpaĝojn. Post traktado de la peto, la sekvaj moduloj ekludas.

Ĉapitro 3. Instalado de la stirilo, lubrikado de la ĉeno

Tuj kiam la analizanto finiĝis, la prizorganto ekludas, donante pliajn instrukciojn al la servilo kaj dividante kontrolon en du partojn.

simpla prizorganto

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

Fakte, ekzistas nur unu kontrolo por uzanta rajtigo, post kiu komenciĝas la petotraktado.

Kliento-Regiloj
Se la uzanto ne estas rajtigita, tiam por li la funkcieco baziĝas nur sur la montrado de uzantprofiloj kaj la rajtiga registra fenestro. La kodo por rajtigita uzanto aspektas proksimume same, do mi ne vidas kialon por duobligi ĝin.

Neaŭtorizita uzanto

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;

}

}

}
}

Kaj kompreneble, la uzanto devas ricevi iom da enhavo de la paĝoj, do por respondoj estas la sekva modulo, kiu respondecas pri respondado al peto pri rimedoj.

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

Sed por montri al la uzanto sian profilon kaj profilojn de aliaj uzantoj, mi decidis uzi RazorEngine, aŭ pli ĝuste parton de ĝi. Ĝi ankaŭ inkluzivas pritrakti malbonajn petojn kaj elsendi la taŭgan erarkodon.

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

Kaj kompreneble, por ke la konfirmo de rajtigitaj uzantoj funkciu, rajtigo estas necesa. La rajtiga modulo interagas kun la datumbazo. La datumoj ricevitaj de la formoj en la retejo estas analizitaj de la kunteksto, la uzanto estas konservita kaj ricevas kuketojn kaj aliron al la servo kontraŭe.

Modulo de rajtigo

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

Kaj jen kiel la datumbazo aspektas:

Datumbazo

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


Kaj ĉio funkcias kiel horloĝo, rajtigo kaj registra laboro, la minimuma funkcieco por aliri la servon jam estas tie kaj estas tempo skribi aplikaĵon kaj ligi la tuton kune kun la ĉefaj funkcioj por kiuj ĉio estas farita.

Ĉapitro 4

Por redukti la laborkostojn de verkado de du aplikoj por du platformoj, mi decidis fari transplatformon sur Xamarin.Forms. Denove, danke al la fakto ke ĝi estas en C#. Farinte testan aplikaĵon, kiu simple sendas datumojn al la servilo, mi renkontis unu interesan momenton. Por peto de la aparato, por amuzo, mi efektivigis ĝin sur HttpClient kaj ĵetis ĝin al la servilo HttpRequestMessage kiu enhavas datumojn de la rajtiga formo en json-formato. Sen atendi ion aparte, mi malfermis la servilan protokolon kaj vidis peton de la aparato kun ĉiuj datumoj tie. Malpeza stuporo, konscio pri ĉio, kio estis farita dum la pasintaj 3 semajnoj de languida vespero. Por kontroli la ĝustecon de la senditaj datumoj, mi kunvenis testan servilon ĉe HttpListner. Ricevinte la sekvan peton jam sur ĝi, mi disigis ĝin en kelkaj linioj de kodo, ricevis la KeyValuePair-datumojn de la formularo. Demanda analizado reduktita al du linioj.

Mi komencis testi plu, ĝi ne estis menciita antaŭe, sed en la antaŭa servilo mi ankoraŭ efektivigis babilejon konstruitan sur retejsockets. Ĝi funkciis sufiĉe bone, sed la principo mem de interago per Tcp estis deprima, tro multe da kromaĵo devis esti produktita por ĝuste konstrui la interagadon de du uzantoj kun la protokolo de korespondado. Ĉi tio inkluzivas analizi peton pri konektoŝanĝo kaj kolekti respondon per la protokolo RFC 6455. Tial, en la testa servilo, mi decidis krei simplan retsocket-konekton. Nur pro intereso.

Babilkonekto

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

Kaj ĝi funkciis. La servilo mem starigis la konekton, generis respondŝlosilon. Mi eĉ ne devis aparte agordi servilan registradon per ssl, sufiĉas, ke la sistemo jam havas atestilon instalitan sur la bezonata haveno.

Sur la aparato kaj sur la retejo, du klientoj interŝanĝis mesaĝojn, ĉio ĉi estis registrita. Neniuj grandegaj analiziloj malrapidigas la servilon, nenio el tio estis postulata. La responda tempo estis reduktita de 200ms al 40-30ms. Kaj mi venis al la sola ĝusta decido.

Altnivela biciklado aŭ kliento-servila aplikaĵo bazita sur C# .Net kadro

Forĵetu la nunan servilan efektivigon sur Tcp kaj reverku ĉion sub Http. Nun la projekto estas en la stadio de restrukturado, sed laŭ tute malsamaj principoj de interagado. La funkciado de aparatoj kaj la retejo estas sinkronigita kaj sencimigita kaj havas komunan koncepton, kun la sola diferenco, ke aparatoj ne bezonas generi html-paĝojn.

konkludo

"Ne konante la vadejon, ne enŝovu vian kapon en la akvon" Mi pensas, ke antaŭ ol komenci labori, mi devus pli klare difini la celojn kaj celojn, kaj ankaŭ enprofundiĝi en la studon de la necesaj teknologioj kaj metodoj por ilia efektivigo ĉe diversaj klientoj. La projekto jam proksimiĝas al finiĝo, sed eble mi revenos por paroli pri tio, kiel mi fuŝis iujn aferojn denove. Mi lernis multon dum la evoluprocezo, sed estas pli por lerni en la estonteco. Se vi legis ĉi tien, do dankon pro legado.

fonto: www.habr.com