pengantar
Semuanya dimulai ketika seorang kolega menyarankan agar saya membuat layanan web kecil. Itu seharusnya menjadi sesuatu seperti sumbu, tetapi untuk kerumunan TI. Fungsinya sangat sederhana, Anda mendaftar, mengisi profil dan pergi ke poin utama, yaitu, menemukan teman bicara dan memperluas koneksi Anda dan mendapatkan kenalan baru.
Disini saya harus ngelantur dan bercerita sedikit tentang diri saya, agar kedepannya semakin jelas mengapa saya mengambil langkah-langkah tersebut dalam pembangunan.
Saat ini, saya memegang posisi sebagai Artis Teknis di salah satu studio game, pengalaman saya dalam pemrograman C # hanya berdasarkan penulisan skrip dan utilitas untuk Unity dan, selain itu, membuat plugin untuk pekerjaan tingkat rendah dengan perangkat android. Saya belum keluar dari dunia kecil ini, dan kemudian kesempatan seperti itu muncul.
Bagian 1. Membuat prototipe bingkai
Setelah memutuskan seperti apa layanan ini, saya mulai mencari opsi untuk implementasi. Cara termudah adalah menemukan semacam solusi siap pakai, yang, seperti burung hantu di bola dunia, dapat ditarik oleh mekanik kami dan menempatkan semuanya pada kecaman publik.
Tetapi ini tidak menarik, saya tidak melihat tantangan dan pengertian apa pun dalam hal ini, dan oleh karena itu saya mulai mempelajari teknologi web dan metode interaksi dengan mereka.
Saya mulai dengan melihat artikel dan dokumentasi C # .Net. Di sini saya menemukan berbagai cara untuk menyelesaikan tugas tersebut. Ada banyak mekanisme untuk berinteraksi dengan jaringan, dari solusi lengkap seperti layanan ASP.Net atau Azure, hingga interaksi langsung dengan koneksi Tcp \ Http.
Setelah melakukan upaya pertama dengan ASP, saya langsung menolaknya, menurut saya itu keputusan yang terlalu sulit untuk layanan kami. Kami tidak akan menggunakan bahkan sepertiga dari kemampuan platform ini, jadi saya terus mencari. Pilihan datang antara klien-server TCP dan Http. Di sini, di Habré, saya menemukan artikel tentang server multithread , setelah mengumpulkan dan menguji yang, saya memutuskan untuk fokus pada interaksi dengan koneksi TCP, untuk beberapa alasan saya pikir http tidak akan memungkinkan saya untuk membuat solusi lintas platform.
Versi pertama server termasuk menangani koneksi, menyajikan konten statis pada halaman web, dan termasuk database pengguna. Dan untuk memulainya, saya memutuskan untuk membangun fungsionalitas untuk bekerja dengan situs, sehingga nantinya pemrosesan aplikasi di android dan ios akan dilampirkan di sini.
Ini beberapa kode
, :
:
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}");
}
}
}
}
}
, , . ( , - ).
Bab 2. Mengencangkan roda
Setelah menguji operasi server, saya sampai pada kesimpulan bahwa ini akan menjadi solusi yang sangat baik ( spoiler: tidak ) untuk layanan kami, sehingga proyek mulai memperoleh logika.
Selangkah demi selangkah modul baru mulai muncul dan fungsionalitas server diperluas. Server mendapat domain uji dan enkripsi ssl untuk koneksi.
Sedikit lagi kode yang menjelaskan logika penanganan server dan klien
, .
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);
}
}
}
Tetapi karena server bekerja secara eksklusif pada koneksi TCP, maka perlu untuk membuat modul yang dapat mengenali konteks permintaan. Saya memutuskan bahwa parser akan cocok di sini, yang akan membagi permintaan dari klien menjadi beberapa bagian yang terpisah, yang dengannya saya dapat berinteraksi untuk memberikan jawaban yang diperlukan kepada klien.
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;
}
}
}
Esensinya terletak pada fakta bahwa menggunakan ekspresi reguler untuk membagi permintaan menjadi beberapa bagian. Kami menerima pesan dari klien, pilih baris pertama, yang berisi metode dan url permintaan. Kemudian kita membaca header, yang kita masukkan ke dalam array dalam bentuk HeaderName = Content, dan juga menemukan, jika ada, konten yang menyertai (misalnya, querystring), yang juga kita masukkan ke dalam array yang serupa. Selain itu, pengurai mengetahui apakah klien saat ini diotorisasi dan menyimpan datanya. Semua permintaan dari klien resmi berisi hash otorisasi, yang disimpan dalam cookie, sehingga Anda dapat memisahkan logika kerja lebih lanjut untuk dua jenis klien dan memberi mereka jawaban yang benar.
Nah, dan fitur kecil dan bagus yang harus dibawa keluar dalam modul terpisah, mengubah permintaan seperti "site.com/@UserName" menjadi halaman pengguna yang dibuat secara dinamis. Setelah memproses permintaan, modul berikut mulai bekerja.
Bab 3. Pemasangan Setang, Pelumasan Rantai
Segera setelah parser selesai bekerja, handler mulai bekerja, memberikan instruksi lebih lanjut ke server dan membagi kontrol menjadi dua bagian.
Penangan sederhana
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);
};
}
}
}
Faktanya, hanya ada satu pemeriksaan untuk otorisasi pengguna, setelah itu pemrosesan permintaan dimulai.
Pengontrol klien
, \. , .
, , , .
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();
}
}
}
}
Dan tentu saja, agar verifikasi pengguna yang sah dapat berfungsi, Anda memerlukan otorisasi. Modul otorisasi berinteraksi dengan database. Data yang diterima dari formulir di situs diurai dari konteksnya, pengguna disimpan dan sebagai gantinya menerima cookie dan akses ke layanan.
Modul otorisasi
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);
}
}
}
}
Dan seperti inilah tampilan pemrosesan database:
Database
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;
}
}
}
}
Dan semuanya bekerja seperti jarum jam, otorisasi dan pendaftaran berfungsi, fungsionalitas minimum akses ke layanan sudah tersedia, dan inilah saatnya untuk menulis aplikasi dan mengikat semuanya dengan fungsi utama yang semuanya dilakukan.
Bab 4. Membuang sepeda
Untuk mengurangi biaya tenaga kerja untuk menulis dua aplikasi untuk dua platform, saya memutuskan untuk membuat cross-platform di Xamarin.Forms. Sekali lagi, karena fakta bahwa itu ada di C #. Setelah membuat aplikasi uji coba yang hanya mengirim data ke server, saya menemukan satu hal menarik. Untuk permintaan dari perangkat, demi kepentingan, saya mengimplementasikannya di HttpClient dan melemparkannya ke server HttpRequestMessage, yang berisi data dari formulir otorisasi dalam format json. Tanpa mengharapkan apa pun, saya membuka log server dan melihat permintaan dari perangkat dengan semua datanya. Sedikit pingsan, kesadaran akan segala sesuatu yang telah dilakukan selama 3 minggu terakhir di malam yang lesu. Untuk memeriksa kebenaran data yang dikirim, saya mengumpulkan server pengujian di HttpListner. Setelah menerima permintaan berikutnya di atasnya, dalam beberapa baris kode saya menguraikannya menjadi beberapa bagian, menerima KeyValuePair data dari formulir.Penguraian kueri dikurangi menjadi dua baris.
Saya mulai menguji lebih lanjut, itu tidak disebutkan sebelumnya, tetapi di server sebelumnya saya masih menerapkan obrolan yang dibangun di websockets. Ini bekerja dengan cukup baik, tetapi prinsip interaksi melalui Tcp sangat menekan, terlalu banyak yang berlebihan harus diproduksi untuk membangun interaksi dua pengguna secara kompeten dengan pemeliharaan log korespondensi. Ini mem-parsing permintaan untuk sakelar koneksi dan mengumpulkan respons menggunakan protokol RFC 6455. Oleh karena itu, di server pengujian, saya memutuskan untuk membuat koneksi websocket sederhana. Murni untuk kesenangan.
Hubungkan ke obrolan
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();
}
}
}
}
}
Dan itu berhasil. Server menyiapkan koneksi itu sendiri, menghasilkan kunci respons. Saya bahkan tidak perlu mengkonfigurasi registrasi server secara terpisah melalui ssl, itu sudah cukup bahwa sertifikat sudah terpasang di sistem pada port yang diperlukan.
Di sisi perangkat dan di sisi situs, dua klien bertukar pesan, semua ini dicatat. Tidak ada pengurai besar yang memperlambat server, semua ini tidak diperlukan. Waktu respons berkurang dari 200ms menjadi 40-30ms. Dan saya sampai pada satu-satunya keputusan yang benar.
Lempar implementasi server saat ini ke Tcp dan tulis ulang semuanya di bawah Http. Sekarang proyek tersebut dalam tahap mendesain ulang, tetapi sudah sesuai dengan prinsip interaksi yang sangat berbeda. Pengoperasian perangkat dan situs disinkronkan dan di-debug serta memiliki konsep umum, dengan satu-satunya perbedaan bahwa perangkat tidak perlu membuat halaman html.
Keluaran
“Tidak mengetahui arungan, jangan menjulurkan kepala ke dalam air” Saya pikir, sebelum mulai bekerja, saya harus lebih jelas mendefinisikan tujuan dan sasaran, serta mempelajari studi tentang teknologi yang diperlukan dan metode penerapannya pada berbagai klien. Proyek ini sudah hampir selesai, tetapi mungkin saya akan kembali untuk membicarakan tentang bagaimana saya mendapatkan beberapa barang lagi. Saya belajar banyak selama proses pengembangan, tetapi masih banyak yang harus dipelajari di masa depan. Jika Anda sudah membaca sejauh ini, terima kasih untuk itu.
