Sissejuhatus
Kõik sai alguse sellest, et kolleeg pakkus mulle välja väikese veebiteenuse loomise. See pidi olema midagi nagu Tinder, aga IT-tööstuse jaoks. Funktsionaalsus on äärmiselt lihtne: registreerud, täidad profiili ja lähed edasi põhisisu juurde, nimelt kaaskandidaadi otsimisele, suhete laiendamisele ja uute tutvuste loomisele.
Siin pean ma natuke taustast rääkima, et hiljem oleks selgem, miks ma just selliseid samme arenduses astusin.
Praeguseks olen ma tehnilise kunstniku ametis ühes mängustuudios ja minu C# programmeerimise kogemus on piiratud ainult skriptide ja utiliitide loomisega Unity jaoks ning lisaks sellele, madalal tasemel pluginatega Android seadmete tööks. Ükski neist maailma ei ole mind välja viinud ja nüüd tuli selline võimalus.
Osa 1. Raami prototüüpimine
Pärast seda, kui olin otsustanud, milline see teenus olema peaks, hakkasin otsima teid selle rakendamiseks. Lihtsaim oleks olnud leida mõni valmis lahendus, millele meie mehhanisme nagu õndsat kulda peale tõmmata ja see kõik avalikult kritiseerimisele esitada.
Aga see ei olnud ju huvitav, ei näinud ma selles mingit väljakutset ja mõtet, seetõttu alustasin veebitehnoloogiate ja nende vahelise suhtlemise õppimist.
Uurimist alustasin lugedes artikleid ja dokumentatsiooni C# .Net kohta. Siit leidsin ma mitmesuguseid viise ülesande täitmiseks. Saadaval on palju mehhanisme võrguga suhtlemiseks, alates täielikest lahendustest nagu ASP.Net või Azure teenused, kuni otse TcpHttp ühenduste kasutamiseni.
Oma esimest katset ASP-iga tegema, lükkasin selle kohe tagasi, kuna minu arvates on see meie teenuse jaoks liiga keeruline lahendus. Me ei kasuta isegi kolmandikku selle platvormi võimalustest, seega jätkasin otsingute tegemist. Valikuks jäi TCP ja Http klient-server. , mille kogusin ja testisin, otsustasin ma peatuda just TCP ühendustega suhtlemisel, kuna mulle tundus, et http ei lase mul luua platvormidevahelist lahendust.
Esimene serveri versioon töötles ühendusi, edastas staatilist sisu veebilehtedelt ja sisaldas kasutajate andmebaasi. Ja alguses otsustasin ma ehitada funktsionaalsuse saidi tööks, et hiljem lisada siia ka rakenduse töötlemine Androidis ja iOSis.
Siin on natuke koodi
Peamine lõng, mis lõputus tsüklis kliente vastu võtab:
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);
}
}
}Kliendihalduri enda rakendus:
kasutades System; kasutades System.IO; kasutades System.Net.Sockets; kasutades System.Text; kasutades System.Text.RegularExpressions; nimekirjast ClearServer { klass Klient { avalik Klient(TcpClient Klient) { string Sõnum = ""; byte[] Puhver = uus byte[1024]; int Arv; while ((Arv = Klient.GetStream().Read(Puhver, 0, Puhver.Length)) > 0) { Sõnum += Encoding.UTF8.GetString(Puhver, 0, Arv); if (Sõnum.IndexOf("rnrn") >= 0 || Sõnum.Length > 4096) { Console.WriteLine(Sõnum); break; } } Match ReqMatch = Regex.Match(Sõnum, @"^w+s+([^s?]+)[^s]*s+HTTP\/.*|"); if (ReqMatch == Match.Empty) { ErrorWorker.SendError(Klient, 400); return; } string TaotluseUri = ReqMatch.Groups[1].Value; TaotluseUri = Uri.UnescapeDataString(TaotluseUri); if (TaotluseUri.IndexOf("..") >= 0) { ErrorWorker.SendError(Klient, 400); return; } if (TaotluseUri.EndsWith("\/")) { TaotluseUri += "index.html"; } string FailiTee ="D:\Web\TestSite{TaotluseUri}";
if (!File.Exists(FailTee))
{
ErrorWorker.SendError(Klient, 404);
return;
}string Laiend = TaotluseUri.Substring(TaotluseUri.LastIndexOf('.'));
string SisuTüüp = "";
switch (Laiend)
{
juhtum ".htm":
juhtum ".html":
SisuTüüp = "text/html";
break;
juhtum ".css":
SisuTüüp = "text/css";
break;
juhtum ".js":
SisuTüüp = "text/javascript";
break;
juhtum ".jpg":
SisuTüüp = "image/jpeg";
break;
juhtum ".jpeg":
juhtum ".png":
juhtum ".gif":
SisuTüüp ="image/{Laiend.Substring(1)}";
break;
default:
if (Laiend.Length > 1)
{
SisuTüüp ="application/{Laiend.Substring(1)}";
}
muul juhul
{
SisuTüüp = "application/unknown";
}
break;
}FailiOtok FS;
proovige
{
FS = uus FileStream(FailiTee, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
ErrorWorker.SendError(Klient, 500);
return;
}string Pealkirjad =
"HTTP/1.1 200 OKnContent-Type: {SisuTüüp}nContent-Length: {FS.Length}nn";
byte[] PealkirjadePuhver = Encoding.ASCII.GetBytes(Pealkirjad);
Klient.GetStream().Write(PealkirjadePuhver, 0, PealkirjadePuhver.Length);while (FS.Position < FS.Length)
{
Arv = FS.Read(Puhver, 0, Puhver.Length);
Klient.GetStream().Write(Puhver, 0, Arv);
}FS.Close();
Klient.Close();
}
}
}
Ja esimene andmebaas, mis on loodud kohaliku SQL-i põhjal:kasutades System; kasutades 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 users = db.GetTable(); foreach (var item in users) { Console.WriteLine("{item.login} {item.password}");"{item.login} {item.password}"
}
}
}
}
}
Kuidas märgata, et see versioon ei erine kuigi palju sellest, mis oli artiklis. Sisuliselt lisandus siia ainult lehtede laadimine arvuti kaustast ja andmebaas (mis muide, selles versioonis ei töötanud vale ühenduse arhitektuuri tõttu).Peatükk 2. Rataste lisamine
Testides serveri tööd, jõudsin järeldusele, et see on suurepärane lahendus (spoiler: ei), meie teenusele, seetõttu hakkas projekt loogikaga täienduma.
Samm-sammult hakkasid ilmuma uued moodulid ja serveri funktsionaalsus kasvas. Server sai omale testdomeeni ja ssl ühenduse krüpteerimise.Veel natuke koodi, mis kirjeldab serveri tööd ja klientide käsitlemist
Uuendatud serveriversioon, mis sisaldab sertifikaadi kasutamist.kasutades System; kasutades System.Net; kasutades System.Net.Sockets; kasutades System.Reflection; kasutades System.Security; kasutades System.Security.Cryptography.X509Certificates; kasutades System.Security.Permissions; kasutades System.Security.Policy; kasutades System.Threading; namespace ClearServer { suletud klass 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("Server alustamine.." + 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("Domeenile üleminek"); 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 uus kliendi töötleja SSL-i autoriseerimisega:
using ClearServer.Core.Requester; using System; using System.Net.Security; using System.Net.Sockets; namespace ClearServer { public class Client { public Client(TcpClient Client) { SslStream SSlClientStream = new SslStream(Client.GetStream(), false); try { SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true); } catch (Exception e) { Console.WriteLine( "---------------------------------------------------------------------n" +"|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Authentication failed - closing the connection.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Klient.Close();
}
new RequestContext(SSlClientStream, Client);
}}
}
Kuna server töötab rangelt TCP-ühenduse põhjal, on vajalik luua moodul, mis suudab tuvastada päringu konteksti. Otsustasin, et siin sobib parser, mis jagab kliendi päringu eraldi osadeks, millega ma saan suhelda, et edastada kliendile vajalikud vastused.Parser
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 HeadersValues; public List FormValues; private TcpClient TcpClient; private event Action 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("{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;
proovige
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
proovige
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
catch { }
}
}
catch { }}
private List HeaderValues()
{
var values = new List();
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 ContentValues()
{
var values = new List();
var output = Message.Trim('\n').Split().Last();
var parse = Regex.Matches(output, @"([^&]*?)=([^&]*?)");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim().Replace('+', ' ');
});
}
return values;
}
}
}
Selle põhiolemus on kasutada regulaaravaldiste abil päringute osadeks jagamist. Saame kliendilt sõnumi, eraldame esimeses reas, kus on päringu meetod ja URL. Seejärel lugeme päiseid, mille paneme massiivi nimega Pealkiri=Sisukord, ning leiame, kui see on olemas, saatva sisu (näiteks querystring), mille samuti paneme sarnasesse massiivi. Lisaks selgitab parser välja, kas praegune klient on autoriseeritud, ja salvestab tema andmed. Kõik autoriseeritud klientide päringud sisaldavad autentimise hashi, mis salvestatakse küpsistesse, tänu millele saab edasist loogikat jagada kahe kliendi tüübi vahel ja anda neile õiged vastused.Ja lihtne, meeldiv funktsioon, mille oleks väärt eraldi moodulisse viia, on päringute teisendamine kujul «site.com/@UserName» dünaamiliselt genereeritud kasutajalehtedeks. Pärast päringu töötlemist astuvad mängu järgmised moodulid.
Peatükk 3. Rooli paigaldamine, ketta määrimine
Niipea kui parser on oma töö lõpetanud, astub mängu töötleja, mis annab serverile edasised juhised ja jagab juhtimise kaheks osaks.
Lihtne töötleja
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); }; } } }Põhimõtteliselt on siin ainult üks autoriseerimise kontroll, pärast mida algab päringu töötlemine.
Kliendikontrollerid
Kui kasutaja ei ole autoriseeritud, siis tema funktsionaalsus põhineb vaid kasutajaprofiilide kuvamisel ja registreerimise/autoriseerimise aknal. Autoriseeritud kasutaja kood näeb välja umbes sama, seega ei näe ma mõtet seda dubleerida.Autoriseerimata kasutaja
kasutades ClearServer.Core.Requester; kasutades System.IO; kasutades System.Net.Security; namespace ClearServer.Core.UserController { sisemine klass NonAuthUserController { private readonly SslStream ClientStream; private readonly RequestContext Context; private readonly WriteController WriteController; private readonly AuthorizationController AuthorizationController; private readonly string ViewPath = "C: /Users /drdre /source /repos /ClearServer /View"; public NonAuthUserController(SslStream clientStream, RequestContext context) { this.ClientStream = clientStream; this.Context = context; this.WriteController = new WriteController(clientStream); this.AuthorizationController = new AuthorizationController(clientStream, context); ResourceLoad(); } void ResourceLoad() { string[] blockextension = new string[] {"cshtml", "html", "htm"}; bool block = false; foreach (var item in blockextension) { if (Context.RequestUrl.Contains(item)) { block = true; break; } } string FilePath = ""; string Header = ""; var RazorController = new RazorController(Context, ClientStream); switch (Context.RequestMethod) { case "GET": switch (Context.RequestUrl) { case " /": FilePath = ViewPath + " /loginForm.html"; Header ="HTTP /1.1 200 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;}
}
}
}
Loomulikult peab kasutaja saama mingit sisu lehtedelt, seega on vastuste jaoks olemas järgmine moodul, mis vastutab ressursside päringutele vastamise eest.WriterController
kasutades System; kasutades System.IO; kasutades System.Net.Security; kasutades System.Text; nimekirjas ClearServer.Core.UserController { avalik klass WriteController { SslStream ClientStream; public WriteController(SslStream ClientStream) { see.ClientStream = ClientStream; } avalik void DefaultWriter(string Header, string FilePath) { FileStream fileStream; proovis { fileStream = uus FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); Header =quot;{Header}nSisu-Pikkus: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = uus byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
catch { }
}avalik string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnSisu-Tüüp:";
switch (extension)
{
juhtum ".html":
juhtum ".htm":
returnquot;{Header} text/html";
juhtum ".css":
returnquot;{Header} text/css";
juhtum ".js":
returnquot;{Header} text/javascript";
juhtum ".jpg":
juhtum ".jpeg":
juhtum ".png":
juhtum ".gif":
returnquot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
returnquot;{Header} application/" + extension.Substring(1);
}
muul juhul
{
returnquot;{Header} application/tundmatu";
}
}
}avalik void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}avalik void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}
Kuid et näidata kasutajale tema profiili ja teiste kasutajate profiile, otsustasin kasutada RazorEngine, täpsemalt selle osa. See hõlmab ka vale päringute töötlemist ja vastava vea koodi väljastamist.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 muidugi, et kontrollida autoriseeritud kasutajaid, on vajalik autentimine. Autentimismoodul suhtleb andmebaasiga. Veebivormidest saadud andmed analüüsitakse kontekstis, kasutaja salvestatakse ja saab sellele vastutasuks küpsised ja ligipääsu teenusele.
Autentimismoodul
using ClearServer.Core.Cookies; using ClearServer.Core.Requester; using ClearServer.Core.Security; using System; using System.Linq; using System.Net.Security; using System.Text; namespace ClearServer.Core.UserController { internal class AuthorizationController { private SslStream ClientStream; private RequestContext Context; private UserCookies cookies; private WriteController WriteController; DatabaseWorker DatabaseWorker; RazorController RazorController; PasswordHasher PasswordHasher; public AuthorizationController(SslStream clientStream, RequestContext context) { ClientStream = clientStream; Context = context; DatabaseWorker = new DatabaseWorker(); WriteController = new WriteController(ClientStream); RazorController = new RazorController(context, clientStream); PasswordHasher = new PasswordHasher(); } internal void MethodRecognizer() { if (Context.FormValues.Count == 2 && Context.FormValues.Any(x => x.Name == "password")) Authorize(); else if (Context.FormValues.Count == 3 && Context.FormValues.Any(x => x.Name == "regPass")) Registration(); else { RazorController.ErrorLoader(401); } } private void Authorize() { var values = Context.FormValues; var user = new User() { login = values[0].Value, password = PasswordHasher.PasswordHash(values[1].Value) }; user = DatabaseWorker.UserAuth(user); if (user != null) { cookies = new UserCookies(user.login, user.password); user.cookie = cookies.AuthCookie; DatabaseWorker.UserUpdate(user); var response = Encoding.UTF8.GetBytes("HTTP/1.1 301 Moved Permanently Location: /@{user.login} Set-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly";
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);}
muul juhul
{
RazorController.ErrorLoader(401);}
}private void Registration()
{
var values = Context.FormValues;
var user = new User()
{
name = values[0].Value,
login = values[1].Value,
password = PasswordHasher.PasswordHash(values[2].Value),
};
cookies = new UserCookies(user.login, user.password);
user.cookie = cookies.AuthCookie;
if (DatabaseWorker.LoginValidate(user.login))
{
Console.WriteLine("User ready");
Console.WriteLine("{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes("HTTP/1.1 301 Moved Permanently Location: /@{user.login} Set-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
muul juhul
{
RazorController.ErrorLoader(401);
}
}
}
}
Nii näeb välja andmebaasi töötlemine:Andmebaas
using ClearServer.Core.UserController; using System; using System.Data.Linq; using System.Linq; namespace ClearServer { class DatabaseWorker { private readonly Table users = null; private readonly DataContext DataBase = null; private const string connectionStr = @"teekandme"; public DatabaseWorker() { DataBase = new DataContext(connectionStr); users = DataBase.GetTable(); } public User UserAuth(User User) { try { var user = users.SingleOrDefault(t => t.login.ToLower() == User.login.ToLower() && t.password == User.password); if (user != null) return user; else return null; } catch (Exception) { return null; } } public void UserRegister(User user) { try { users.InsertOnSubmit(user); DataBase.SubmitChanges(); Console.WriteLine("User {user.name} with id {user.uid} added");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(Console.WriteLine("User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
foreach (var item in users)
{
Console.WriteLine(item.login + "\n");
}
}
public User CookieValidate(string CookieInput)
{
User user = null;
proovige
{
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;
proovige
{
user = users.Single(x => x.login.ToLower() == login.ToLower());
if (user != null)
{
return user;
}
muul juhul
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}
Ja kõik töötab nagu kellavärk, autentimine ja registreerimine toimivad, minimaalne funktsionaalsus teenusele juurdepääsuks on juba olemas ning aeg on hakata rakendust kirjutama ja siduma seda kõike peamiste funktsioonidega, mille tõttu seda kõike tehakse.Peatükk 4. Rataste viskamine
Ette kirjutada kahte rakendust kahele platvormile, otsustasin luua ristplatvormi Xamarin.Forms'iga. Taaskord tänu sellele, et see on C#. Kui tegin testrakenduse, mis lihtsalt saadab serverisse andmeid, kohtasin üht huvitavat hetke. Seadmest päringu tegemiseks rakendasin selle uudishimu tõttu HttpClient'i ja saatsin serverile HttpRequestMessage'i, milles olid autoriseerimise vormi andmed JSON-formaadis. Oodates erilisi tulemusi avasin serveri logi ja nägin sealt seadme päringut koos kõigi andmetega. Kerge šokk, arusaamine kõigest, mis oli viimase kolme nädala jooksul toimunud rahututel õhtutel. Kontrollimaks edastatud andmete õigsust, kogusin testserveri HttpListener'iga. Saades järgmist päringut juba sellel, eraldasin paaril koodireal selle osadeks, saades KeyValuePair andmed vormist. Päringu analüüs vähenes kahe reale.
Alustasin katsetamist edasi, varasemalt mainimata, kuid eelmises serveris rakendasin ka vestluse, mis põhines veebipulkadel. See töötas päris hästi, kuid suhtlusprotsess TCP kaudu oli masendav, liiga palju tarbetut tuli genereerida, et õigesti üles ehitada kahe kasutaja vaheline suhtlemine vestluse logi pidamisega. See hõlmas päringu analüüsimist ühenduse lülitamiseks ja vastuse kogumist vastavalt RFC 6455 protokollile. Seetõttu otsustasin testserveris luua lihtsa veebipulga ühenduse. Puhtalt huvi pärast.
Ühendamine vestlusega
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(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 see hakkas tööle. Server seadistas ühenduse automaatselt, genereeris vastusvõtme. Mul ei olnud isegi vaja eraldi seadistada serveri registreerimist ssl-iga, piisab sellest, et süsteemis on juba õige sadama sertifikaat paigaldatud.
Seadmest ja veebilehelt vahetasid kaks klienti sõnumeid, kõike seda logiti. Suuri parsereid, mis serveri tööd aeglustaks, ei olnud vaja. Vastusaeg lühenes 200 ms pealt 40-30 ms peale. Ja ma jõudsin ainukese õige lahenduseni.
Käesolev serveri rakendus Tcp-l põhjal välja visata ja kõik Http alla ümber kirjutada. Projekt on nüüd uue projekteerimise staadiumis, kuid täiesti erinevate suhtlemispõhimõtete alusel. Seadmise ja veebilehe töö on sünkroniseeritud ja parandatud ning omab ühist kontseptsiooni, pidades silmas, et seadmetele ei ole vaja genereerida html lehti.
Kokkuvõte
„Ei tasu minna vette teadmata, kui sügavale see on“ Arvan, enne tööde alustamist, oleksin pidanud selgemini määratlema eesmärgid ja ülesanded ning süvenema vajalike tehnoloogiate ja nende rakendamise meetodite uurimisse erinevates klientides. Projekt on juba lõpetamise lävel, kuid võib-olla naasen, et rääkida, kuidas ma jälle teatud asjadega eksisin. Olen arenguprotsessis palju õppinud, kuid veelgi enam on veel õppida. Kui olete siia hetkeks peatunud, siis tänan teid selle eest.
Allikas: habr.com

