Entry
Allt började med att en kollega föreslog att jag skulle skapa en liten webbtjänst. Det var tänkt att vara något som liknar Tinder, men för IT-publiken. Funktionaliteten är extremt enkel: du registrerar dig, fyller i din profil och går vidare till huvudpunkten, nämligen att hitta en samtalspartner och utöka dina kontakter och göra nya bekantskaper.
Här borde jag göra en utvikning och berätta lite om mig själv, så att det senare skulle bli tydligare varför jag tog just dessa steg i utvecklingen.
För tillfället innehar jag positionen som Technical Artist i en spelstudio, min erfarenhet av programmering i C# byggdes endast på att skriva skript och verktyg för Unity och utöver detta skapa plugins för arbete på låg nivå med Android-enheter. Jag hade aldrig vågat mig bortom denna lilla värld förut, och nu dök en sådan möjlighet upp.
Del 1. Frame prototyping
Efter att ha bestämt mig för vad den här tjänsten skulle vara, började jag leta efter alternativ för implementering. Det enklaste vore att hitta någon form av färdig lösning, som vi, som en uggla på en jordglob, kunde tänja på vår mekanik och lägga ut det hela för allmänt misstroende.
Men det här är inte intressant, jag såg ingen utmaning eller mening i det, och därför började jag studera webbteknologier och metoder för interaktion med dem.
Jag började studera genom att titta på artiklar och dokumentation på C# .Net. Här hittade jag olika sätt att utföra uppgiften. Det finns många mekanismer för att interagera med nätverket, från fullfjädrade lösningar som ASP.Net eller Azure-tjänster, till direkt interaktion med TcpHttp-anslutningar.
Efter att ha gjort mitt första försök med ASP, avvisade jag det omedelbart; enligt min mening var det ett för svårt beslut för vår tjänst. Vi kommer inte att använda ens en tredjedel av den här plattformens möjligheter, så jag fortsatte att söka. Valet stod mellan TCP och Http klient-server. Här, på Habr, stötte jag på en artikel om , efter att ha monterat och testat vilket jag bestämde mig för att fokusera på interaktion med TCP-anslutningar, av någon anledning trodde jag att http inte skulle tillåta mig att skapa en plattformsoberoende lösning.
Den första versionen av servern inkluderade anslutningsbearbetning, serverade statiskt webbsideinnehåll och inkluderade en användardatabas. Och till att börja med bestämde jag mig för att bygga funktionalitet för att arbeta med sajten, så att jag senare även kunde bifoga bearbetning av applikationen på Android och iOS.
Här är lite kod
Huvudtråden, i en oändlig slinga, som accepterar kunder:
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);
}
}
}
Själva klienthanteraren:
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();
}
}
}
Och den första databasen byggd på lokal 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}");
}
}
}
}
}
Som du kan se skiljer sig denna version lite från den i artikeln. Faktum är att bara laddning av sidor från en mapp på datorn och en databas (som för övrigt inte fungerade i denna version, på grund av felaktig anslutningsarkitektur) lades till här.Kapitel 2. Skruva på hjulen
Efter att ha testat serverns funktion kom jag fram till att detta skulle vara en utmärkt lösning(spoiler: nej), för vår tjänst, så projektet började få logik.
Steg för steg började nya moduler dyka upp och serverns funktionalitet utökades. Servern förvärvade en testdomän och ssl anslutningskryptering.Lite mer kod som beskriver logiken för servern och klientens bearbetning
En uppdaterad version av servern som inkluderar användningen av ett certifikat.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(); } } }Och även en ny klienthanterare med SSL-auktorisering:
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);
}}
}
Men eftersom servern enbart fungerar på en TCP-anslutning är det nödvändigt att skapa en modul som kan känna igen förfrågningskontexten. Jag bestämde mig för att en parser skulle vara lämplig här, som skulle dela upp klientens begäran i separata delar, som jag kunde interagera med för att ge klienten de nödvändiga svaren.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<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;
}
}
}
Dess kärna är att använda reguljära uttryck för att dela upp en fråga i delar. Vi får ett meddelande från klienten, välj den första raden, som innehåller metoden och begäran url. Sedan läser vi rubrikerna, som vi kör in i en array av formen HeaderName=Content, och hittar även, om det finns något, tillhörande innehåll (till exempel querystring), som vi också kör in i en liknande array. Dessutom tar parsern reda på om den aktuella klienten är auktoriserad och sparar hans data. Alla förfrågningar från auktoriserade kunder innehåller en auktoriseringshash, som lagras i cookies, tack vare vilken det är möjligt att separera ytterligare arbetslogik för två typer av klienter och ge dem korrekta svar.Tja, och en liten, trevlig funktion som skulle vara värd att lägga in i en separat modul, konvertera förfrågningar som "site.com/@UserName" till dynamiskt genererade användarsidor. När förfrågan har behandlats kommer följande moduler in i bilden.
Kapitel 3. Installation av ratten, smörjning av kedjan
När parsern har slutfört sitt arbete kommer hanteraren in i spelet, ger ytterligare instruktioner till servern och delar upp kontrollen i två delar.
Enkel hanterare
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); }; } } }I huvudsak finns det bara en kontroll för användarauktorisering, varefter begärandebehandlingen börjar.
Klientkontrollanter
Om användaren inte är auktoriserad baseras funktionaliteten för honom endast på att visa användarprofiler och registrerings-/auktoriseringsfönstret. Koden för en auktoriserad användare ser ungefär likadan ut, så jag ser ingen mening med att duplicera den.Obehörig användare
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;}
}
}
}
Och självklart måste användaren få en del innehåll från sidorna, så för svar finns följande modul, ansvarig för att svara på en resursbegäran.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();
}
}
}
Men för att visa användaren sin profil och andra användares profiler bestämde jag mig för att använda RazorEngine, eller snarare en del av det. Det inkluderar också att hantera ogiltiga förfrågningar och utfärda en lämplig felkod.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(); } } } }Och naturligtvis krävs auktorisation för att verifieringen av auktoriserade användare ska fungera. Auktoriseringsmodulen interagerar med databasen. Uppgifterna som tas emot från formulären på webbplatsen tolkas från sammanhanget, användaren sparas och får cookies och tillgång till tjänsten i gengäld.
Auktoriseringsmodul
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);
}
}
}
}
Och så här ser databasbearbetning ut:databas
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;
}
}
}
}
Och allt fungerar som urverk, auktoriserings- och registreringsarbete, minimifunktionaliteten för åtkomst till tjänsten är redan tillgänglig och det är dags att skriva en ansökan och knyta ihop allt det här med de grundläggande funktionerna som allt är gjort för.Kapitel 4. Kasta cykeln
För att minska arbetskostnaderna för att skriva två ansökningar för två plattformar bestämde jag mig för att göra en cross-platform på Xamarin.Forms. Återigen, tack vare att det är i C#. Efter att ha gjort en testapplikation som helt enkelt skickar data till servern, stötte jag på en intressant punkt. För förfrågan från enheten, för skojs skull, implementerade jag den på HttpClient och skickade den till servern som HttpRequestMessage, som innehåller data från auktoriseringsformuläret i json-format. Jag förväntade mig inte något särskilt, jag öppnade serverloggen och såg en förfrågan från enheten med all data. En lätt stupor, insikten om allt som hade gjorts under de senaste 3 tröga veckorna på kvällen. För att kontrollera riktigheten av den skickade datan, monterade jag en testserver på HttpListner. Efter att ha fått en annan förfrågan redan på den, tog jag isär den i ett par rader kod och fick KeyValuePair data från formuläret. Frågetolkningen har reducerats till två rader.
Jag började testa vidare, det nämndes inte tidigare, men på den tidigare servern implementerade jag även en chatt byggd på webbsockets. Det fungerade ganska bra, men principen för interaktion via Tcp var deprimerande, för mycket onödiga saker måste skapas för att korrekt bygga interaktion mellan två användare med att föra en logg över korrespondens. Detta inkluderar analys av en begäran om anslutningsbyte och insamling av ett svar med RFC 6455-protokollet. Därför bestämde jag mig för att skapa en enkel websocket-anslutning i testservern. Bara för skojs skull.
Ansluter till chatt
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(); } } } } }Och det fungerade. Servern själv konfigurerade anslutningen och genererade svarsnyckeln. Jag behövde inte ens separat konfigurera serverregistreringen via SSL, det räckte att systemet redan hade ett certifikat installerat på den port som krävs.
På enhetssidan och på webbplatssidan utbytte två klienter meddelanden, som alla loggades. Inga stora parsers saktar ner servern, inget av det krävdes. Svarstiden har reducerats från 200ms till 40-30ms. Och jag kom till det enda rätta beslutet.
Kasta bort den nuvarande implementeringen av servern på Tcp och skriv om allt för Http. Nu håller projektet på att göras om, men med helt andra principer för interaktion. Driften av enheterna och sajten är synkroniserad och felsökt och har ett gemensamt koncept, med den enda skillnaden är att det inte finns något behov av att generera HTML-sidor för enheterna.
Utgång
"Gå inte i vattnet utan att veta djupet." Jag tror att innan jag börjar arbeta borde jag ha tydligare definierat målen och målen, samt fördjupat mig i studien av nödvändiga teknologier och metoder för deras implementering på olika kunder. Projektet närmar sig redan sitt slut, men jag kanske kommer tillbaka igen för att berätta om hur jag skruvat till den eller den igen. Jag har lärt mig mycket under utvecklingsprocessen, men det finns fortfarande mer att lära framåt. Om du har läst så här långt, tack för det.
Källa: will.com

