Začetek
Vse se je začelo, ko mi je kolega predlagal, da naredim majhen spletni servis. To naj bi bilo nekaj podobnega tinderju, vendar za IT hangout. Funkcionalnost je popolnoma enostavna, registrirate se, izpolnite profil in nadaljujete z bistvom, in sicer iskanjem sogovornika ter širjenjem povezav in sklepanjem novih poznanstev.
Tukaj se moram malo oddaljiti in povedati nekaj o sebi, da bo v prihodnje bolj jasno, zakaj sem naredil takšne korake v razvoju.
Trenutno sem na položaju tehničnega umetnika v igralnem studiu, moje izkušnje s programiranjem C# pa so temeljile le na pisanju skriptov in pripomočkov za Unity ter poleg tega na ustvarjanju vtičnikov za nizko nivojsko delo z android napravami. Izven tega malega sveta še nisem izbral in potem zasukal takšne priložnosti.
1. del. Izdelava okvirnih prototipov
Ko sem se odločil, kakšna bo ta storitev, sem začel iskati možnosti za izvedbo. Najlažje bi bilo poiskati neko že pripravljeno rešitev, na katero bi lahko kot sova na globusu potegnili naše mehanike in vse skupaj postavili v javno grajo.
Ampak to ni zanimivo, v tem nisem videl nobenega izziva in smisla, zato sem začel preučevati spletne tehnologije in metode interakcije z njimi.
Študija se je začela z ogledom člankov in dokumentacije o C # .Net. Tu sem našel različne načine za izpolnitev naloge. Obstaja veliko mehanizmov za interakcijo z omrežjem, od popolnih rešitev, kot so storitve ASP.Net ali Azure, do neposredne interakcije s povezavami TcpHttp.
Po prvem poskusu z ASP sem ga takoj preklical, po mojem mnenju je bila to pretežka odločitev za naš servis. Ne bomo izkoristili niti tretjine zmogljivosti te platforme, zato sem nadaljeval z iskanjem. Izbira je nastala med TCP in Http odjemalec-strežnik. Tukaj, na Habréju, sem naletel na članek o , ki sem jih zbral in preizkusil, sem se odločil, da se osredotočim na interakcijo s povezavami TCP, iz nekega razloga sem mislil, da mi http ne bo omogočil ustvarjanja rešitve za več platform.
Prva različica strežnika je vključevala upravljanje povezav, streženje statične vsebine spletne strani in vključevanje baze podatkov uporabnikov. In za začetek sem se odločil zgraditi funkcionalnost za delo s spletnim mestom, da bi kasneje lahko tukaj povezal obdelavo aplikacij na android in ios.
Tukaj je nekaj kode
Glavna nit, ki sprejema odjemalce v neskončni zanki:
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);
}
}
}
Sam upravljavec odjemalca:
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();
}
}
}
In prva baza podatkov, zgrajena na lokalnem 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}");
}
}
}
}
}
Kot lahko vidite, se ta različica malo razlikuje od tiste v članku. Pravzaprav je bilo tukaj dodano samo nalaganje strani iz mape na računalniku in podatkovne baze (ki mimogrede v tej različici zaradi napačne arhitekture povezave ni delovala).2. poglavje
Po testiranju strežnika sem prišel do zaključka, da bi bila to odlična rešitev (spojler: št), za našo storitev, tako da je projekt začel dobivati logiko.
Korak za korakom so se začeli pojavljati novi moduli in funkcionalnost strežnika se je širila. Strežnik je pridobil testno domeno in ssl šifriranje povezave.Še malo kode, ki opisuje logiko strežnika in obdelavo strank
Posodobljena različica strežnika, vključno z uporabo certifikata.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(); } } }Kot tudi nov upravljavec strank z avtorizacijo prek 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);
}}
}
Ker pa strežnik deluje izključno na TCP povezavi, je treba izdelati modul, ki bi prepoznal kontekst zahteve. Odločil sem se, da je tukaj primeren razčlenjevalec, ki bo zahtevo odjemalca razdelil na ločene dele, s katerimi lahko komuniciram, da odjemalcu dam potrebne odgovore.razčlenjevalnik
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;
}
}
}
Njegovo bistvo je v tem, da s pomočjo regularnih izrazov zahtevo razdelimo na dele. Od stranke prejmemo sporočilo, izberemo prvo vrstico, ki vsebuje metodo in url zahteve. Nato preberemo glave, ki jih poženemo v matriko oblike ImeGlave = Vsebina, in poiščemo tudi, če obstaja, spremljajočo vsebino (na primer poizvedbeni niz), ki jo prav tako poženemo v podobno matriko. Poleg tega razčlenjevalnik ugotovi, ali je trenutni odjemalec pooblaščen in shrani njegove podatke. Vse zahteve pooblaščenih odjemalcev vsebujejo avtorizacijski hash, ki je shranjen v piškotkih, zaradi česar je možno ločiti nadaljnjo logiko dela za dve vrsti odjemalcev in jima dati pravilne odgovore.No, majhna, prijetna funkcija, ki bi jo bilo treba premakniti v ločen modul, ki pretvarja zahteve, kot je "site.com/@UserName", v dinamično ustvarjene uporabniške strani. Po obdelavi zahteve pridejo v poštev naslednji moduli.
Poglavje 3. Namestitev krmila, mazanje verige
Takoj, ko je razčlenjevalnik končan, pride v igro upravljavec, ki daje nadaljnja navodila strežniku in razdeli nadzor na dva dela.
preprost vodnik
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); }; } } }Pravzaprav obstaja samo eno preverjanje avtorizacije uporabnika, po katerem se začne obdelava zahteve.
Krmilniki strank
Če uporabnik ni avtoriziran, potem zanj funkcionalnost temelji le na prikazu uporabniških profilov in avtorizacijskem registracijskem oknu. Koda za pooblaščenega uporabnika izgleda približno enako, zato ne vidim razloga za podvajanje.Nepooblaščen uporabnik
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;}
}
}
}
In seveda mora uporabnik prejeti nekaj vsebine strani, zato je za odgovore na voljo naslednji modul, ki je odgovoren za odgovor na zahtevo po virih.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":
returnquot;{Header} text/html";
case ".css":
returnquot;{Header} text/css";
case ".js":
returnquot;{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
returnquot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
returnquot;{Header} application/" + extension.Substring(1);
}
else
{
returnquot;{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();
}
}
}
Da pa bi uporabniku prikazal njegov profil in profile drugih uporabnikov, sem se odločil uporabiti RazorEngine oziroma njegov del. Vključuje tudi obravnavo slabih zahtev in izdajo ustrezne kode napake.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(); } } } }In seveda, da bi preverjanje pooblaščenih uporabnikov delovalo, je potrebna avtorizacija. Avtorizacijski modul je v interakciji z bazo podatkov. Podatki, prejeti iz obrazcev na spletnem mestu, se razčlenijo iz konteksta, uporabnik se shrani in v zameno prejme piškotke in dostop do storitve.
Avtorizacijski modul
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);
}
}
}
}
In baza podatkov izgleda takole:Baza podatkov
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;
}
}
}
}
In vse deluje kot ura, avtorizacija in registracija, minimalna funkcionalnost dostopa do storitve je že na voljo in čas je, da napišete aplikacijo in povežete celotno stvar z glavnimi funkcijami, za katere je vse narejeno.4. poglavje
Da bi zmanjšal stroške dela pri pisanju dveh aplikacij za dve platformi, sem se odločil narediti večplatformno na Xamarin.Forms. Spet zahvaljujoč dejstvu, da je v C#. Ko sem naredil testno aplikacijo, ki preprosto pošilja podatke na strežnik, sem naletel na en zanimiv trenutek. Za zahtevo iz naprave sem za hec implementiral na HttpClient in vrgel na strežnik HttpRequestMessage, ki vsebuje podatke iz avtorizacijskega obrazca v json formatu. Ne da bi pričakoval kaj posebnega, sem odprl dnevnik strežnika in tam videl zahtevo naprave z vsemi podatki. Rahla omamljenost, zavedanje vsega, kar je bilo storjeno v zadnjih 3 tednih dolgočasnega večera. Za preverjanje pravilnosti poslanih podatkov sem sestavil testni strežnik na HttpListner. Ko sem že prejel naslednjo zahtevo, sem jo razdelil v nekaj vrsticah kode, iz obrazca dobil podatke KeyValuePair. Razčlenjevanje poizvedbe zmanjšano na dve vrstici.
Začel sem s testiranjem naprej, prej ni bilo omenjeno, vendar sem na prejšnjem strežniku vseeno implementiral klepet, zgrajen na spletnih vtičnicah. Delovalo je precej dobro, vendar je bil sam princip interakcije prek Tcp depresiven, preveč dodatnega je bilo treba proizvesti, da bi pravilno zgradili interakcijo dveh uporabnikov z beleženjem korespondence. To vključuje razčlenjevanje zahteve za preklapljanje povezave in zbiranje odgovora s protokolom RFC 6455. Zato sem se v testnem strežniku odločil ustvariti preprosto povezavo websocket. Čisto zaradi interesa.
Klepetalna povezava
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(); } } } } }In uspelo je. Strežnik je sam vzpostavil povezavo, generiral odgovorni ključ. Sploh mi ni bilo treba posebej konfigurirati registracije strežnika preko ssl, dovolj je, da ima sistem že nameščen certifikat na zahtevanih vratih.
Na strani naprave in na strani strani sta si dve stranki izmenjali sporočila, vse to je bilo zabeleženo. Brez ogromnih razčlenjevalnikov, ki bi upočasnili strežnik, nič od tega ni bilo potrebno. Odzivni čas se je zmanjšal z 200 ms na 40-30 ms. In prišel sem do edine prave odločitve.
Zavrzite trenutno implementacijo strežnika na Tcp in vse prepišite pod Http. Zdaj je projekt v fazi prenove, vendar po popolnoma drugačnih principih interakcije. Delovanje naprav in spletnega mesta je sinhronizirano in razhroščeno ter ima skupen koncept, le da napravam ni treba generirati html strani.
Izhod
"Če ne poznaš broda, ne tišči glave v vodo" Mislim, da bi moral pred začetkom dela bolj jasno opredeliti cilje in cilje ter se poglobiti v študijo potrebnih tehnologij in metod za njihovo implementacijo na različne stranke. Projekt je že pri koncu, mogoče pa se še vrnem, da se pogovorim o tem, kako sem nekatere stvari spet zajebal. Med razvojnim procesom sem se veliko naučil, a v prihodnosti se moram še več naučiti. Če ste prebrali tako daleč, potem hvala za branje.
Vir: www.habr.com

