Manapság szinte minden elérhetÅ‘ HTTP vagy HTTPS kapcsolat segÃtségével. Számos webes REST szolgáltatás érhetÅ‘ el, amit beépÃthetünk a programunkba. A .NET beépÃtetten rendelkezik a WebClient osztállyal, ami HTTP/HTTPS/FTP kapcsolatok kezelésére képes. SegÃtségével fájlokat tudunk az internetrÅ‘l letölteni és feltölteni.
A WebClient implementációjában a Windows beépÃtett HTTP/FTP kliensét alkalmazza. Ennek vonzata, hogy az osztály implementálja az IDisposable felületet. Maga az osztály számos metódust biztosÃt szinkron és esemény vezérelt aszinkron letöltés megvalósÃtására.
Az alábbi példa egy egyszerű REST API hÃvást mutat be: a https://api.ipify.org cÃm lekérése visszaadja a látogató publikus IP cÃmét.
using System;
using System.Net;
namespace PeldaHttp
{
class Program
{
static void Main(string[] args)
{
using (var webClient = new WebClient())
{
//Proxy konfigurálása, ha van
IWebProxy proxy = WebRequest.DefaultWebProxy;
proxy.Credentials = CredentialCache.DefaultCredentials;
webClient.Proxy = proxy;
//publikus IP cÃm lekérése
string ip = webClient.DownloadString("https://api.ipify.org");
//konvertálás .NET IPAddress osztályba
IPAddress address = IPAddress.Parse(ip);
Console.WriteLine("IP: {0}, Family: {1}", address, address.AddressFamily);
Console.WriteLine("\nHost name:");
//DNS host név megállapÃtása a cÃmbÅ‘l
IPHostEntry entry = Dns.GetHostEntry(address);
if (entry.AddressList.Length > 0)
{
Console.WriteLine(entry.HostName);
Console.WriteLine("Ip adresses:");
foreach (IPAddress item in entry.AddressList)
{
//sikeres találat esetén legalább
//1db ip tartozik egy host névhez
Console.WriteLine(item);
}
}
}
Console.ReadKey();
}
}
}
A program egy lehetséges kimenete:
IP: 91.12.230.26, Family: InterNetwork
Host name:
91-12-230-26.pool.netszolgaltato.hu
Ip adresses:
91.12.230.26
A példa a WebClient példányosÃtása után a kliens számára bekonfigurálja a rendszer szinten beállÃtott proxy cÃmet, valamint hozzáférés azonosÃtónak a Windows felhasználónevet és jelszót. A proxy konfigurálására a legtöbb esetben semmi szükség, viszont a céges hálózatokon szokás proxy szerveren keresztülvezetni a kéréseket.
A DownloadString szinkron hÃvással letöltÅ‘dik az IP cÃm. Aszinkron hÃvásra itt nincs szükség, mert a válasz csak pár byte méretű. Ezután a szöveges formában kapott IP cÃmet a program átkonvertálja egy IPAddress tÃpusú objektumba, ami a .NET keretrendszerben IPv4 és IPv6 cÃmek leÃrására szolgáló osztály.
Ezt a cÃmet felhasználva egy fordÃtott DNS keresést indÃt a példaprogram. Ez azt jelenti, hogy a cÃm ismeretében vagyunk kÃváncsiak a host névre és nem a host név ismeretében a cÃmre. A DNS működésébÅ‘l adódóan egy host névhez több IP cÃm is tartozhat. Ezért a program a cÃmek listáját egy ciklussal bejárva jelenÃti meg.
A fenti példa tökéletes kisebb méretű REST API hÃvásokhoz, de mi van akkor ha egy nagy méretű fájlt szeretnénk letölteni, aminek a letöltése számos percet, vagy akár órát is igénybe vehet? Ekkor csak az aszinkron letöltési megközelÃtés jöhet szóba.
A WebClient esetén az aszinkron programozás eseményekkel és egy háttérben futó szállal van megvalósÃtva. Ez mára már egy elavult koncepciónak számÃt, fÅ‘leg REST API hÃvások esetén. Éppen ezért a .NET 4.5 megjelenésével a keretrendszer kapott egy HttpClient osztályt, ami a System.Net.Http névtérben lakik és kifejezetten HTTP REST API hÃvások lebonyolÃtására lett kitalálva. ElÅ‘nye a WebClient osztályhoz képest, hogy hÃvások között megtartja a bejelentkezési adatokat, valamint a nyelvi szintű aszinkron programozást támogatja. Cserébe viszont csak HTTP protokollt támogat és nincs letöltési visszajelzése.
Éppen ezért nagy fájlok (méret > 1MiB) letöltésére még a mai napig a WebClient az ajánlott megoldás. Az alábbi példaprogram az aszinkron letöltést mutatja be.
using System;
using System.ComponentModel;
using System.Net;
namespace PeldaHttp2
{
class Program
{
static void Main(string[] args)
{
using (var client = new WebClient())
{
client.DownloadProgressChanged += Client_DownloadProgressChanged;
client.DownloadFileCompleted += Client_DownloadFileCompleted;
Uri cim = new Uri("http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso");
client.DownloadFileAsync(cim, "d:\\ubuntu1804.iso");
Console.ReadKey();
}
}
private static void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Console.Write("{0} / {1} bytes, {2}%...\r", e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
}
private static void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
Console.WriteLine("Done");
}
}
}
A program kimenete:
1996488704 / 1996488704 bytes, 100%...
A példaprogram a könyv Ãrásának pillanatában elérhetÅ‘ LTS Ubuntu Linux telepÃtÅ‘t tölti le. A DownloadProgressChanged eseménykezelÅ‘ minden egyes alkalommal lefuttatásra kerül, ha a letöltés változott. Az eseménykezelÅ‘ e paraméterén keresztül információt kapunk a letöltött byte-ok számáról, a teljes méretrÅ‘l byte-ban és a letöltés százalékos állapotáról. A DownloadFileCompleted esemény akkor kerül meghÃvásra, ha fájl a letöltése véget ért.
A példában újdonság az Uri osztály, ami a .NET keretrendszerben URL cÃmek ellenÅ‘rzésére és kezelésére van kitalálva. Az URL cÃmek lényegében szövegek, de speciális jelentéssel. Az Uri osztály ezekrÅ‘l ad információt a felhasználóinak. Többek között kinyerhetÅ‘ belÅ‘le a protokoll, a host cÃm és a host cÃmen belül az elérési útvonal.
Ennek HTTP és HTTPS esetén nem feltétlen van értelme ennyire részletesen, de ha a cÃm FTP protokollt alkalmaz, akkor értelmet nyer a dolog, mert elsÅ‘ körben a szerverhez kell csatlakozni a host név alapján, majd elkérni a fájlt.
HTTPClient
A WebClient osztály a .NET-ben az elsÅ‘ kiadás óta jelen van. Azonban a megjelenése óta a web sokat változott, illetve bevezetésre került az async/await a Task osztályok kezeléséhez. A WebClient egyszerű HTTP kommunikáció lebonyolÃtására lett tervezve, amikor még a REST API használat abszolút nem volt jellemzÅ‘. Éppen ezért a .NET 4.5-ben az async/await-el együtt bemutatkozott a HTTPClient osztály, ami aszinkron módon HTTP kommunikációt valósÃt meg. Az új osztály létrehozását az is motiválta, hogy a WebClient FTP protokollt is támogat, aminek a használata 2012 környékén sokkal kevésbé volt jellemzÅ‘ már, mint a .NET 1 megjelenésekor.
Egy darabig mind a WebClient és HTTPClient hasonló tudással rendelkezett, de a web változásával (TLS 1.2, HTTP/2, HTTP/3) az újdonságokat csak a HTTPClient kapta meg és a .NET Core 3.1 óta nem javasolt a WebClient alkalmazása olyannyira, hogy .NET 6 óta fordÃtói figyelmeztetést is kapunk, hogy a használata elavult.
Nem kell azonban megijedni, mivel a HTTPClient használata igencsak hasonló. Nézzük is meg, hogy az IP cÃmet lekérÅ‘ kis programunk hogyan néz ki a HTTPClient használatával:
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace PeldaHttpClient
{
class Program
{
static async Task Main(string[] args)
{
//Proxy konfigurálása, ha van
IWebProxy? proxy = WebRequest.DefaultWebProxy;
if (proxy != null)
{
proxy.Credentials = CredentialCache.DefaultCredentials;
}
HttpClientHandler httpClientHandler = new HttpClientHandler
{
Proxy = proxy
};
using (var httpClient = new HttpClient(httpClientHandler))
{
//publikus IP cÃm lekérése
string ip = await httpClient.GetStringAsync("https://api.ipify.org");
//konvertálás .NET IPAddress osztályba
IPAddress address = IPAddress.Parse(ip);
Console.WriteLine("IP: {0}, Family: {1}", address, address.AddressFamily);
Console.WriteLine("\nHost name:");
//DNS host név megállapÃtása a cÃmbÅ‘l
IPHostEntry entry = Dns.GetHostEntry(address);
if (entry.AddressList.Length > 0)
{
Console.WriteLine(entry.HostName);
Console.WriteLine("Ip adresses:");
foreach (IPAddress item in entry.AddressList)
{
//sikeres találat esetén legalább
//1db ip tartozik egy host névhez
Console.WriteLine(item);
}
}
}
Console.ReadKey();
}
}
}
A legnagyobb változás a fenti kódban az, hogy a HttpClient letöltÅ‘ metódusát GetStringAsync-nak hÃvják és ez egy Task<string> tÃpust ad vissza, illetve a proxy konfigurálását másképpen kell elvégezni.
De mi van akkor, ha fájlokat akarunk letölteni? Ekkor is alkalmazható a HTTPClient. Itt azonban egy picivel bonyolultabb kódot kapunk, mint a WebClient esetén:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace PeldaHttp2
{
class Program
{
static async Task Main(string[] args)
{
using (var client = new HttpClient())
{
//böngésző user agent konfigurálása
const string agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36";
client.DefaultRequestHeaders.UserAgent.ParseAdd(agent);
Uri cim = new Uri("http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso");
//http kérés, a HTTP fejlécek megérkezéséig olvasunk
using (var response = await client.GetAsync(cim, HttpCompletionOption.ResponseHeadersRead))
{
//Ok http ellenőrzése
response.EnsureSuccessStatusCode();
long length = response.Content.Headers.ContentLength ?? 0;
int read = 0;
long done = 0;
var buffer = new byte[4096];
//válasz stream másolása fájlba
using (var soruce = await response.Content.ReadAsStreamAsync())
{
using (var target = File.Create("d:\\ubuntu1804.iso"))
{
do
{
read = await soruce.ReadAsync(buffer, 0, buffer.Length);
await target.WriteAsync(buffer, 0, read);
done += read;
Report(length, done);
}
while (read > 0);
}
}
}
Console.WriteLine();
Console.WriteLine("Done");
Console.ReadKey();
}
}
private static void Report(long length, long done)
{
float percent = (float)done / length;
Console.Write("{0} / {1} bytes, {2:P}...\r", done, length, percent);
}
}
}
A fenti kódban az agent konfigurálására azért van szükség, mert bizonyos oldalak csak specifikus böngészőket engednek fel az oldalra. Ezt általában biztonsági megfontolásból szokták megtenni.
A GetAsync metódussal egy HTTP választ kérünk, fontos, hogy a HttpCompletionOption.ResponseHeadersRead be legyen állÃtva, fÅ‘leg ha nagy fájlokat töltünk le. Ennek az oka az, hogy a HTTP kérésre adott válasz a fejlécekbÅ‘l (Headers) és a tartalomból (Content) áll. Ha nem konfiguráljuk be, hogy a GetAsync sikeres a fejlécek megérkezésekor, akkor a teljes tartalmat le fogja tölteni a memóriába, ami sok ideig tarthat és sok memóriát is emészthet fel.
A fejlécek megérkezése után a válaszon hÃvott EnsureSuccessStatusCode() biztosÃtja, hogy a szerver válasza 200-as Ok kódú, ami azt jelenti, hogy a fájl létezik. Ha más kódot kapnánk vissza, akkor ez a metódus kivételt dobna.
A fájl mérete a fejléceken keresztül kérdezhetÅ‘ le a response.Content.Headers.ContentLength tulajdonság kiolvasásával. Ez egy long? tÃpus, ami annak köszönhetÅ‘, hogy a HTTP fejlécben nem kötelezÅ‘ megadni a válasz hosszát, ha az darabokban érkezik.1 Ezt követÅ‘en a fájl tartalmának beszerzése egy hagyományos Stream másolás.
-
Ezt az átviteli módot leginkább Videó streaming közben használják.↩