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.↩