Edistyksellinen pyöräily tai asiakaspalvelinsovellus, joka perustuu C# .Net -kehykseen

Merkintä

Kaikki alkoi siitä, että kollega ehdotti, että luoisin pienen verkkopalvelun. Sen piti olla jotain Tinderin kaltaista, mutta IT-joukolle. Toiminto on äärimmäisen yksinkertainen, rekisteröidyt, täytät profiilin ja siirryt pääasiaan eli juttelevan henkilön löytämiseen ja yhteyksien laajentamiseen sekä uusien tuttavuuksien solmimiseen.

Tässä minun täytyy vetäytyä ja kertoa hieman itsestäni, jotta tulevaisuudessa olisi selvempää, miksi otin tällaisia ​​kehitysaskeleita.

Tällä hetkellä toimin teknisenä taiteilijana yhdessä pelistudiossa, ohjelmointikokemukseni C#-kielellä rakentui vain Unityn skriptien ja apuohjelmien kirjoittamiseen ja tämän lisäksi lisäosien tekemiseen matalan tason työhön Android-laitteiden kanssa. En ollut vielä uskaltanut tämän pienen maailman ulkopuolelle, ja sitten sellainen tilaisuus avautui.

Osa 1. Kehyksen prototyyppi

Päätettyään millainen tämä palvelu olisi, aloin etsiä toteutusvaihtoehtoja. Helpoin asia olisi löytää jonkinlainen valmis ratkaisu, johon, kuten pöllö maapallolle, voidaan vetää mekaniikkamme ja koko asia altistua julkiselle epäluottamukselle.
Mutta tämä ei ole kiinnostavaa, en nähnyt siinä mitään haastetta tai järkeä, ja siksi aloin tutkia verkkoteknologioita ja menetelmiä vuorovaikutukseen niiden kanssa.

Aloitin opiskelun katsomalla artikkeleita ja dokumentaatiota C# .Netissä. Täältä löysin erilaisia ​​tapoja suorittaa tehtävä. Verkon kanssa vuorovaikutukseen on monia mekanismeja täysimittaisista ratkaisuista, kuten ASP.Net- tai Azure-palveluista, suora vuorovaikutus TcpHttp-yhteyksien kanssa.

Ensimmäisen yritykseni ASP:llä hylkäsin sen heti, tämä oli mielestäni liian vaikea päätös palvelullemme. Emme käyttäisi kolmannestakaan tämän alustan ominaisuuksista, joten jatkoin hakua. Valinta oli TCP:n ja HTTP:n asiakaspalvelimen välillä. Täällä, Habressa, törmäsin artikkeliin aiheesta monisäikeinen palvelin, kerättyään ja testattuani sen, päätin keskittyä nimenomaan vuorovaikutukseen TCP-yhteyksien kanssa, jostain syystä luulin, että http ei anna minun luoda cross-platform-ratkaisua.

Palvelimen ensimmäinen versio sisälsi yhteydenkäsittelyn, palveli staattista verkkosivun sisältöä ja sisälsi käyttäjätietokannan. Ja aluksi päätin rakentaa toimintoja sivuston kanssa työskentelyä varten, jotta voin myöhemmin lisätä sovelluksen käsittelyn Android- ja iOS-laitteille.

Tässä vähän koodia
Pääsäie vastaanottaa asiakkaita loputtomassa silmukassa:

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

Asiakaskäsittelijä itse:

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

Ja ensimmäinen paikalliseen SQL:ään rakennettu tietokanta:

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

Kuten näette, tämä versio eroaa vähän artikkelin versiosta. Itse asiassa lisäsimme tänne vain sivujen lataamisen tietokoneen kansiosta ja tietokannasta (joka muuten ei toiminut tässä versiossa väärän yhteysarkkitehtuurin vuoksi).

Luku 2. Pyörien ruuvaaminen

Testattuani palvelinta tulin siihen tulokseen, että tämä olisi erinomainen ratkaisu(spoileri: ei), palvelullemme, joten projekti alkoi saada logiikkaa.
Uusia moduuleja alkoi ilmestyä askel askeleelta ja palvelimen toiminnallisuus laajeni. Palvelin on hankkinut testitoimialueen ja SSL-yhteyden salauksen.

Hieman enemmän koodia, joka kuvaa palvelimen ja asiakkaan käsittelyn logiikkaa
Palvelimen päivitetty versio, joka sisältää varmenteen käytön.

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

Ja myös uusi asiakaskäsittelijä, jolla on SSL-valtuutus:

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

}
}

Mutta koska palvelin toimii yksinomaan TCP-yhteydellä, on tarpeen luoda moduuli, joka tunnistaa pyynnön kontekstin. Päätin, että tähän sopisi jäsentäjä, joka hajottaa asiakkaan pyynnön erillisiin osiin, joiden kanssa voisin olla vuorovaikutuksessa ja antaa asiakkaalle tarvittavat vastaukset.

jäsentäjä

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

Sen ydin on jakaa pyyntö osiin säännöllisten lausekkeiden avulla. Saamme asiakkaalta viestin, valitse ensimmäinen rivi, joka sisältää menetelmän ja pyynnön URL-osoitteen. Sitten luemme otsikot, jotka laitamme taulukkoon, jonka muoto on HeaderName=Content, ja löydämme myös mahdollisen mukana tulevan sisällön (esimerkiksi kyselymerkkijonon), jonka myös laitamme samanlaiseen taulukkoon. Lisäksi jäsentäjä selvittää, onko nykyinen asiakas valtuutettu, ja tallentaa hänen tietonsa. Kaikki valtuutettujen asiakkaiden pyynnöt sisältävät valtuutustiivisteen, joka tallennetaan evästeisiin, jonka ansiosta on mahdollista erottaa kahden asiakastyypin lisätoimintalogiikka ja antaa niille oikeat vastaukset.

No, pieni, mukava ominaisuus, joka kannattaisi laittaa erilliseen moduuliin, kyselyiden, kuten "site.com/@Käyttäjänimi" muuntaminen dynaamisesti luoduiksi käyttäjäsivuiksi. Pyynnön käsittelyn jälkeen seuraavat moduulit tulevat käyttöön.

Luku 3. Ohjauspyörän asennus, ketjun voitelu

Heti kun jäsentäjä on saanut työnsä valmiiksi, käsittelijä tulee peliin ja antaa lisäohjeita palvelimelle ja jakaa ohjauksen kahteen osaan.

Yksinkertainen käsittelijä

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

Itse asiassa käyttäjän valtuutus tarkistetaan vain kerran, jonka jälkeen pyynnön käsittely alkaa.

Asiakasohjaimet
Jos käyttäjä ei ole valtuutettu, hänen toiminnallisuus perustuu vain käyttäjäprofiilien näyttöön ja valtuutuksen rekisteröintiikkunaan. Valtuutetun käyttäjän koodi näyttää suunnilleen samalta, joten en näe syytä kopioida sitä.

Luvaton käyttäjä

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;

}

}

}
}

Ja tietysti käyttäjän on saatava jonkinlainen sivun sisältö, joten vastauksille on olemassa seuraava moduuli, joka vastaa resurssipyyntöihin vastaamisesta.

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

Mutta näyttääkseni käyttäjälle hänen profiilinsa ja muiden käyttäjien profiilit, päätin käyttää RazorEngineä tai pikemminkin osaa siitä. Se sisältää myös virheellisten pyyntöjen käsittelyn ja asianmukaisen virhekoodin antamisen.

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

Ja tietysti, jotta valtuutettujen käyttäjien todentaminen toimisi, tarvitaan valtuutus. Valtuutusmoduuli on vuorovaikutuksessa tietokannan kanssa. Sivuston lomakkeista vastaanotetut tiedot jäsennetään kontekstista, käyttäjä tallennetaan ja vastineeksi hän saa evästeitä ja pääsyn palveluun.

Valtuutusmoduuli

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

Ja tältä tietokannan käsittely näyttää:

tietokanta

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


Ja kaikki toimii kuin kellonkello, valtuutus- ja rekisteröintityö, minimitoiminnallisuus palveluun pääsyä varten on jo olemassa ja on tullut aika kirjoittaa hakemus ja sitoa koko juttu yhteen päätoimintoihin, joita varten kaikkea tehdään.

Luku 4. Pyörän heittäminen pois

Vähentääkseni työvoimakustannuksia kahden sovelluksen kirjoittamisesta kahdelle alustalle, päätin tehdä cross-platformin Xamarin.Formsille. Jälleen, kiitos siitä, että se on C#. Tehtyäni testisovelluksen, joka yksinkertaisesti lähettää tietoja palvelimelle, törmäsin mielenkiintoiseen kohtaan. Laitteen pyynnölle huvin vuoksi toteutin sen HttpClientissä ja lähetin sen HttpRequestMessage-palvelimelle, joka sisältää tiedot valtuutuslomakkeesta json-muodossa. Mitään erityisemmin odottamatta avasin palvelinlokin ja näin siellä pyynnön laitteelta, jossa oli kaikki tiedot. Pieni stupor, tietoisuus kaikesta, mitä on tehty viimeisten 3 viikon aikana levoton ilta. Lähetettyjen tietojen tarkkuuden tarkistamiseksi kokosin testipalvelimen HttpListneriin. Saatuani siihen jo toisen pyynnön, erotin sen parilla rivillä koodia ja sain lomakkeelta KeyValuePair-tietoja. Kyselyn jäsentäminen väheni kahdelle riville.

Aloin testaamaan lisää, sitä ei aiemmin mainittu, mutta edellisellä palvelimella toteutin myös websocketeille rakennetun chatin. Se toimi varsin hyvin, mutta jo Tcp:n kautta tapahtuvan vuorovaikutuksen periaate oli masentava, jouduttiin tekemään liikaa turhaa työtä kahden käyttäjän vuorovaikutuksen rakentamiseksi pätevästi kirjeenvaihtolokin kanssa. Tämä sisältää yhteyden vaihtopyynnön jäsentämisen ja vastauksen keräämisen protokollalla RFC 6455. Siksi päätin luoda testipalvelimessa yksinkertaisen websocket-yhteyden. Huvin vuoksi.

Yhdistä chattiin

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

Ja se toimi. Palvelin itse määritti yhteyden ja loi vastausavaimen. Palvelimen rekisteröintiä SSL:n kautta ei tarvinnut erikseen konfiguroida, vaan riitti, että järjestelmään oli jo asennettu varmenne vaadittuun porttiin.

Laitepuolella ja sivuston puolella kaksi asiakasta vaihtoivat viestejä, kaikki tämä kirjattiin. Ei suuria jäsentimiä hidastamassa palvelinta, mitään näistä ei vaadittu. Vastausaika on lyhentynyt 200 ms:sta 40-30 ms:iin. Ja tein ainoan oikean päätöksen.

Edistyksellinen pyöräily tai asiakaspalvelinsovellus, joka perustuu C# .Net -kehykseen

Heitä pois nykyinen palvelintoteutus Tcp:ssä ja kirjoita kaikki uudelleen Http:n alle. Nyt projekti on uudelleensuunnitteluvaiheessa, mutta täysin erilaisten vuorovaikutusperiaatteiden mukaan. Laitteiden ja sivuston toiminta on synkronoitu ja virheenkorjattu ja sillä on yhteinen konsepti, ainoana erona on, että laitteille ei tarvitse luoda HTML-sivuja.

johtopäätös

"Jos et tunne kaalaa, älä mene veteen" Mielestäni ennen työn aloittamista minulla pitäisi olla selkeämmin määritellyt tavoitteet ja tavoitteet sekä syventyä tarvittavien teknologioiden ja menetelmien tutkimukseen niiden toteuttamiseksi eri asiakkaille. Projekti on jo loppusuoralla, mutta ehkä palaan taas keskustelemaan siitä, kuinka säästin tiettyjä asioita. Opin paljon kehitysprosessin aikana, mutta tulevaisuudessa on vielä enemmän opittavaa. Jos olet lukenut tähän asti, kiitos siitä.

Lähde: will.com