Einführung
Alles begann, als ein Kollege vorschlug, einen kleinen Webdienst zu erstellen. Es sollte so etwas wie ein Zunder sein, aber für die IT-Masse. Die Funktionalität ist äußerst einfach: Sie registrieren sich, füllen ein Profil aus und gehen zum Hauptpunkt, nämlich einen Gesprächspartner zu finden, Ihre Verbindungen zu erweitern und neue Bekanntschaften zu schließen.
Hier muss ich abschweifen und ein wenig über mich erzählen, damit in Zukunft klarer wird, warum ich solche Entwicklungsschritte unternommen habe.
Im Moment habe ich die Position eines technischen Künstlers in einem Spielestudio inne. Meine Erfahrung in der C # -Programmierung basierte nur auf dem Schreiben von Skripten und Dienstprogrammen für Unity und dem Erstellen von Plugins für die Arbeit mit Android-Geräten auf niedriger Ebene. Ich bin noch nicht aus dieser kleinen Welt herausgekommen, und dann ergab sich eine solche Gelegenheit.
Teil 1. Prototyping des Rahmens
Nachdem ich mich für diesen Service entschieden hatte, suchte ich nach Optionen für die Implementierung. Der einfachste Weg wäre, eine vorgefertigte Lösung zu finden, die wie eine Eule auf einem Globus von unseren Mechanikern gezogen werden kann und das Ganze öffentlich kritisiert.
Aber das ist nicht interessant, ich sah darin keine Herausforderung und keinen Sinn, und deshalb begann ich, Webtechnologien und Interaktionsmethoden mit ihnen zu studieren.
Ich habe mir zunächst die Artikel und die Dokumentation zu C # .Net angesehen. Hier habe ich verschiedene Wege gefunden, um die Aufgabe zu erfüllen. Es gibt viele Mechanismen für die Interaktion mit dem Netzwerk, von vollwertigen Lösungen wie ASP.Net- oder Azure-Diensten bis hin zur direkten Interaktion mit Tcp \ Http-Verbindungen.
Nachdem ich den ersten Versuch mit ASP unternommen hatte, lehnte ich ihn sofort ab. Meiner Meinung nach war es eine zu schwierige Entscheidung für unseren Dienst. Wir werden nicht einmal ein Drittel der Funktionen dieser Plattform nutzen, daher habe ich weiter gesucht. Die Wahl fiel zwischen TCP und HTTP-Client-Server. Hier auf Habré stieß ich auf einen Artikel über einen Multithread-Server , der gesammelt und getestet wurde. Ich entschied mich, mich auf die Interaktion mit TCP-Verbindungen zu konzentrieren, aus irgendeinem Grund dachte ich, dass http mir nicht erlauben würde, eine plattformübergreifende Lösung zu erstellen.
Die erste Version des Servers umfasste die Verarbeitung von Verbindungen, die Bereitstellung statischer Inhalte auf Webseiten und die Verwendung einer Benutzerdatenbank. Und für den Anfang habe ich beschlossen, eine Funktion für die Arbeit mit der Website zu erstellen, damit später die Bearbeitung der Anwendung auf Android und iOS hier angehängt wird.
Hier ist ein Code
, :
:
local SQL:
, , . ( , - ).
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);
}
}
}
:
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("\r\n\r\n") >= 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 = $"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 = $"image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType = $"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 = $"HTTP/1.1 200 OK\nContent-Type: {ContentType}\nContent-Length: {FS.Length}\n\n";
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();
}
}
}
local 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($"{item.login} {item.password}");
}
}
}
}
}
, , . ( , - ).
Kapitel 2. Räder festziehen
Nachdem ich den Serverbetrieb getestet hatte, kam ich zu dem Schluss, dass dies eine hervorragende Lösung ( Spoiler: Nein ) für unseren Service sein würde, und so begann das Projekt, Logik zu erwerben.
Schritt für Schritt tauchten neue Module auf und die Funktionalität des Servers wurde erweitert. Der Server verfügt über eine Testdomäne und eine SSL-Verschlüsselung der Verbindung.
Etwas mehr Code, der die Logik der Server- und Client-Behandlung beschreibt
, .
ssl:
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:\ssl\itinder.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();
}
}
}
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" +
$"|{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);
}
}
}
Da der Server jedoch ausschließlich mit einer TCP-Verbindung arbeitet, muss ein Modul erstellt werden, das den Anforderungskontext erkennt. Ich entschied, dass hier ein Parser geeignet wäre, der die Anfrage des Kunden in separate Teile aufteilt, mit denen ich interagieren kann, um dem Kunden die erforderlichen Antworten zu geben.
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($"\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;
}
}
}
Sein Kern liegt in der Tatsache, dass reguläre Ausdrücke verwendet werden, um die Anforderung in Teile aufzuteilen. Wir erhalten eine Nachricht vom Kunden, wählen Sie die erste Zeile aus, die die Methode und die URL der Anfrage enthält. Dann lesen wir die Header, die wir in ein Array der Form HeaderName = Content einfügen, und finden gegebenenfalls zugehörigen Inhalt (z. B. Querystring), den wir auch in ein ähnliches Array einspeisen. Außerdem findet der Parser heraus, ob der aktuelle Client autorisiert ist, und speichert seine Daten. Alle Anfragen von autorisierten Clients enthalten einen Autorisierungs-Hash, der in Cookies gespeichert ist, sodass Sie die weitere Arbeitslogik für zwei Arten von Clients trennen und ihnen die richtigen Antworten geben können.
Nun, und eine kleine, nette Funktion, die in einem separaten Modul entfernt werden sollte und Anforderungen wie "site.com/@UserName" in dynamisch generierte Benutzerseiten konvertiert. Nach der Bearbeitung der Anfrage kommen folgende Module ins Spiel.
Kapitel 3. Lenkerinstallation, Kettenschmierung
Sobald der Parser seine Arbeit beendet hat, kommt der Handler ins Spiel, gibt dem Server weitere Anweisungen und teilt die Steuerung in zwei Teile.
Einfacher Handler
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);
};
}
}
}
Tatsächlich gibt es nur eine Prüfung für die Benutzerautorisierung, nach der die Anforderungsverarbeitung beginnt.
Client-Controller
, \. , .
, , , .
RazorEngine, . .
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 = $"HTTP/1.1 200 OK\nContent-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;
}
}
}
}
, , , .
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 = $"{Header}\nContent-Length: {fileStream.Length}\n\n";
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 OK\nContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
return $"{Header} text/html";
case ".css":
return $"{Header} text/css";
case ".js":
return $"{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
return $"{Header} image/{extension}";
default:
if (extension.Length > 1)
{
return $"{Header} application/" + extension.Substring(1);
}
else
{
return $"{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();
}
}
}
RazorEngine, . .
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();
}
}
}
}
Und natürlich benötigen Sie eine Autorisierung, damit die Überprüfung autorisierter Benutzer funktioniert. Das Autorisierungsmodul interagiert mit der Datenbank. Die von den Formularen auf der Website empfangenen Daten werden aus dem Kontext analysiert, der Benutzer wird gespeichert und erhält im Gegenzug Cookies und Zugriff auf den Dienst.
Autorisierungsmodul
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\nLocation: /@{user.login}\nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
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($"{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes($"HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
else
{
RazorController.ErrorLoader(401);
}
}
}
}
Und so sieht die Datenbankverarbeitung aus:
Datenbank
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($"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($"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;
}
}
}
}
Und alles funktioniert wie am Schnürchen, Autorisierung und Registrierung funktionieren, die Mindestfunktionalität für den Zugriff auf den Dienst ist bereits verfügbar, und es ist Zeit, eine Anwendung zu schreiben und das Ganze mit den Hauptfunktionen zu verknüpfen, für die alles erledigt ist.
Kapitel 4. Das Fahrrad wegwerfen
Um die Arbeitskosten für das Schreiben von zwei Anwendungen für zwei Plattformen zu senken, habe ich mich für eine plattformübergreifende Erstellung von Xamarin.Forms entschieden. Wieder aufgrund der Tatsache, dass es in C # ist. Nachdem ich eine Testanwendung erstellt hatte, die einfach Daten an den Server sendet, stieß ich auf einen interessanten Punkt. Aus Interesse habe ich eine Anfrage vom Gerät auf HttpClient implementiert und auf den Server HttpRequestMessage geworfen, der Daten aus dem Autorisierungsformular im JSON-Format enthält. Ohne etwas zu erwarten, öffnete ich das Serverprotokoll und sah eine Anfrage vom Gerät mit allen Daten. Eine leichte Betäubung, die Erkenntnis von allem, was in den letzten 3 Wochen eines trägen Abends getan wurde. Um die Richtigkeit der gesendeten Daten zu überprüfen, habe ich einen Testserver auf HttpListner gesammelt. Nachdem ich die nächste Anfrage bereits darauf erhalten hatte, analysierte ich sie in ein paar Codezeilen in Teile und erhielt das KeyValuePair von Daten aus dem Formular.Das Parsen der Abfrage wurde auf zwei Zeilen reduziert.
Ich habe weiter getestet, es wurde nicht früher erwähnt, aber auf dem vorherigen Server habe ich auch einen Chat implementiert, der auf Websockets basiert. Es hat ziemlich gut funktioniert, aber das Prinzip der Interaktion über Tcp war deprimierend. Es musste zu viel Überflüssiges produziert werden, um die Interaktion zweier Benutzer mit der Pflege eines Korrespondenzprotokolls kompetent aufzubauen. Hierbei wird eine Anforderung für einen Verbindungsschalter analysiert und eine Antwort mithilfe des RFC 6455-Protokolls erfasst. Daher habe ich mich auf dem Testserver entschieden, eine einfache Websocket-Verbindung zu erstellen. Nur zum Spaß.
Verbindung zum Chat herstellen
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();
}
}
}
}
}
Und es hat funktioniert. Der Server hat die Verbindung selbst eingerichtet und einen Antwortschlüssel generiert. Ich musste die Serverregistrierung nicht einmal separat über ssl konfigurieren, es reichte aus, dass das Zertifikat bereits im System am gewünschten Port installiert war.
Auf der Geräteseite und auf der Standortseite tauschten zwei Clients Nachrichten aus, die alle protokolliert wurden. Keine großen Parser, die den Server verlangsamen, nichts davon war erforderlich. Die Reaktionszeit ist von 200 ms auf 40 bis 30 ms gesunken. Und ich bin zu der einzig richtigen Entscheidung gekommen.
Werfen Sie die aktuelle Server-Implementierung auf Tcp und schreiben Sie alles unter HTTP neu. Jetzt befindet sich das Projekt in der Phase der Neugestaltung, jedoch bereits nach völlig anderen Interaktionsprinzipien. Der Betrieb von Geräten und der Site ist synchronisiert und debuggt und hat ein gemeinsames Konzept, mit dem einzigen Unterschied, dass Sie keine HTML-Seiten für Geräte generieren müssen.
Ausgabe
„Wenn Sie die Furt nicht kennen, stecken Sie Ihren Kopf nicht ins Wasser“, denke ich, bevor ich mit der Arbeit beginne, hätte ich die Ziele klarer definieren und mich mit den notwendigen Technologien und Methoden ihrer Implementierung bei verschiedenen Kunden befassen sollen. Das Projekt steht bereits kurz vor dem Abschluss, aber vielleicht komme ich zurück, um darüber zu sprechen, wie ich wieder ein paar Sachen bekommen habe. Ich habe während des Entwicklungsprozesses viel gelernt, aber in Zukunft gibt es noch viel zu lernen. Wenn Sie so weit gelesen haben, danke dafür.
