HTTP (HyperText Transfer Protocol) je internetový protokol navržený původně pro přenášení dokumentů ve formátu HTML. Přestože se dnes tento protokol používá hlavně pro přenášení webových stránek, velmi často se také využívá i pro přenášení jiných dat, např. souborů, obrázků. Protokol je laicky řečeno jazyk, kterým komunikuje webový prohlížeč se serverem. V tomto článku se podíváme, jak tento protokol funguje, a hlavně jak jej používat z prostředí VB.NET, protože to se nám může velmi často hodit.
Jak HTTP funguje?
Nehodlám zde popisovat přesně všechny detaily protokolu HTTP, od toho je specifikace a navíc by to vydalo na několik článků. Záměrně spoustu věcí zjednoduším a vysvětlím jen nezbytné minimum, které by měl znát každý.
Pokud napíšete do webového prohlížeče adresu http://www.vbnet.cz, začne poměrně složitý proces, jehož výsledkem je to, že se vám po chvíli zobrazí náš web. Jak to ale funguje přesně?
- Prohlížeč si zjistí IP adresu serveru vbnet.cz a připojí se k tomuto serveru na port 80 pomocí TCP/IP spojení.
- Jakmile je spojení vytvořeno, prohlížeč zašle serveru požadavek (request), ve kterém řekne, kterou stránku by chtěl, a prozradí o sobě pár údajů, jako třeba verzi prohlížeče, verzi a jazyk operačního systému a pár dalších věcí.
- Server požadavek zpracuje a pošle HTML kód úvodní stránky našeho serveru.
- Prohlížeč stránku stáhne a zjistí, že pro kompletní zobrazení bude potřebovat ještě nějaké obrázky, soubor CSS se styly, nějaké soubory s javascriptem atd., o které si zažádá úplně stejně jako o úvodní stránku.
- Jakmile server všechna požadovaná data zašle, prohlížeč konečně zobrazí úvodní stránku.
I tento seznam je velmi zjednodušený, ale nám bude prozatím stačit. Existují 3 verze protokolu HTTP, a to verze 0.9, verze 1.0 a verze 1.1, která se používá nejčastěji. My si přesnější princip ukážeme na verzi 1.1, uvidíme, jak komunikace pomocí HTTP funguje.
Když se můj prohlížeč připojí k serveru vbnet.cz, zašle mu přibližně takovýto požadavek (některé hlavičky jsem vymazal):
GET / HTTP/1.1
Host: vbnet.cz
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; Media Center PC 5.0; .NET CLR 3.5.21022)
Proxy-Connection: Keep-Alive
Důležitý je první řádek, na kterém vidíme příkaz GET, dále adresu, kterou chceme získat (v našem případě jen lomítko, které značí úvodní stránku; kdybychom chtěli obrázek, musíme napsat třeba images/star.gif) a nakonec verzi protokolu HTTP/1.1. Na dalších řádcích jsou hlavičky, které prozrazují o prohlížeči nějaké další informace. Celý požadavek končí dvěma konci řádku (pro ukončení řádku se používá sekvence znaků 13 a 10, ve VB.NET to je konstanta vbCrLf).
Server tento požadavek přečte a vrátí následující odpověď (response):
HTTP/1.1 200 OK
Cache-Control: private
Date: Sat, 02 Feb 2008 10:56:17 GMT
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: Stats=LastDisplayed=Sat, 02 Feb 2008 11:56:17 GMT; expires=Mon, 03-Mar-2008 10:56:17 GMT; path=/
Set-Cookie: menuState=; expires=Sun, 02-Mar-2008 10:56:17 GMT; path=/
Content-Length: 53124
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
...
Na prvním řádku je stavový kód 200, které značí, že je vše v pořádku. Obecně platí čím větší číslo, tím větší průšvih. Pokud číslo začíná dvojkou, je většinou vše v pořádku, pokud začíná trojkou, znamená to, že stránka existuje, ale je někde jined (přesměrování), pokud začíná číslicí 4, je to problém, protože stránka neexistuje nebo k ní nemáme oprávnění. Pokud kód chyby začíná číslem 5, znamená to nějakou chybu na serveru. Za kódem chyby je i krátký popis.
Pak následuje spousta hlaviček, které nám prozradí informace o serveru, dobu platnosti stránky, použité kódování a případně kompresi, a dále třeba počet bajtů kódu stránky. Za hlavičkami jsou opět dvě ukončení řádku a pak již následují samotná data.
Jak stáhnout soubor ve VB.NET jednoduše
Jistě jste už někdy potřebovali v aplikaci stáhnout nějak soubor z Internetu přes HTTP. Teď již zhruba víte, jak protokol HTTP funguje, takže byste mohli vzít třídu TcpClient, napsat připojení k serveru na port 80, sestavit ručně HTTP request, poslat jej a pak si z odpovědi vytáhnout požadovaná data. Vzhledem k tomu, že HTTP se používá opravdu hojně, je logické, že pokaždé psát tuto funkcionalitu by nikoho nebavilo a navíc v tom můžete udělat spoustu chyb. Proto .NET framework obsahuje třídy, které použití protokolu HTTP implementují a jejich použití je snazší.
Nejjednodušší metodou je použít metodu DownloadFile. Skvělé, pokud potřebujete stáhnout soubor a uložit jej na disk. Nemáte ale pak možnost zjistit, kolik procent souboru už je staženo, nemůžete stahování přerušit atd. Na jednoduché věci se to ale může hodit:
My.Computer.Network.DownloadFile("http://www.vbnet.cz", "d:\vbnet.html")
Jak stáhnout data z HTTP se vší parádou
Někdy se může hodit stáhnout data, ale neukládat je na disk. Ukládání na disk je často zbytečné a navíc musíte vytvářet dočasné soubory, které byste po sobě měli mazat, což mimo jiné odvádí pozornost od konkrétních problémů, kteréžto vaše aplikace řeší. K tomuto účelu slouží třídy HttpWebRequest a HttpWebResponse, první z nich vystaví a odešle požadavek a vrátí vám druhou, která nese odpověď serveru. Nahoru do souboru přidejte řádek Imports System.Net, všechny třídy totiž patří do tohoto namespace.
'vystavit požadavek
Dim rq As HttpWebRequest = HttpWebRequest.Create("http://www.vbnet.cz/")
'odeslat jej na server a získat odpověď
Dim rs As HttpWebResponse = rq.GetResponse()
'přečíst stream až do konce
Using sr As New IO.StreamReader(rs.GetResponseStream())
'vypsat odpověď
MsgBox(sr.ReadToEnd())
End Using
'zavřít spojení
rs.Close()
V první části vytvoříme proměnnou typu HttpWebRequest (pozor na ni, nemá konstruktor, takže žádné New, ale přiřadíme do ní výsledek volání metody HttpWebRequest.Create, které předáme požadovanou adresu URL. Proměnné rq můžeme pak ještě nastavovat hlavičky atd., jakmile jsme s tím hotovi (v našem případě jsme žádné hlavičky nepřidávali), zavoláme metodu GetResponse, která odešle požadavek na server a vrátí objekt HttpWebResponse, který nese odpověď serveru.
Z tohoto objektu, který jsme přiřadili do proměnné rs, můžeme zjišťovat opět hodnoty hlaviček, nás však ale hlavně zajímají data požadavku. Ta jsou bohužel k dispozici pouze ve formě datového proudu (stream), který má metody Read a Write pracující pouze s poli bajtů. To se nám příliš nehodí v případě, že pracujeme s textovými daty. Pokud bychom stahovali obrázek s binárními daty, převádění na String je pitomost! My ale textová data máme, takže s němi chceme pracovat trochu na výši. Naštěstí v .NET frameworku existuje třída IO.StreamReader. My jsme ji používali pro čtení ze souborů, ale pokud v konstruktoru místo jména souboru předáme nějaký Stream, umí tato třída číst z tohoto streamu data a převádět je na String. Hodit se nám mohou hlavně metody ReadLine anebo ReadToEnd.
Nakonec je vhodné zavolat metodu Close na objektu HttpWebResponse, abychom ukončili spojení se serverem.
Ukázková aplikace - jak zjistit aktuální kurz dolaru?
Česká národní banka poskytuje na svém webu v textovém formátu aktuální kurzy některých měn, konkrétně na adrese http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt?date=01.02.2008. Důležité je akorát na konci adresy nastavit správné aktuální datum. Pokud si tento odkaz otevřete v prohlížeči, uvidíte, v jakém formátu jsou informace vráceny. Stačí odpověď projít po řádcích a pokud řádek bude začínat znaky USA|, pak už stačí najít poslední znak | v tomto řádku a přečíst data od něj až do konce. Tím získáme aktuální kurz amerického dolaru. Kód by mohl vypadat následovně:
Dim kurz As Single = 0
'vystavit požadavek na dnešní den
Dim url As String = String.Format("http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt?date={0:dd.MM.yyyy}", Now)
Dim rq As HttpWebRequest = HttpWebRequest.Create(url)
'odeslat jej na server a získat odpověď
Dim rs As HttpWebResponse = rq.GetResponse()
'vytvořit StreamReader pro pohodlnou práci
Dim sr As New IO.StreamReader(rs.GetResponseStream())
'projít řádky
While Not sr.EndOfStream
Dim s As String = sr.ReadLine()
'pokud jsme na řádku s dolarem, vytáhnout z něj kurz
If s.StartsWith("USA|") Then
kurz = CDbl(s.Substring(s.LastIndexOf("|") + 1))
End If
End While
'zavřít StreamReader
sr.Close()
'zavřít spojení
rs.Close()
'vypsat aktuální kurz
MsgBox(String.Format("Aktuální kurz: 1 USD = {0:c}", kurz))
Myslím, že kód je bohatě komentovaný, takže jej není třeba nijak složitě vysvětlovat.
Jak odesílat data metodou POST?
V některých případech chceme serveru poskytnout i další informace, aby mohl svoji odpověď upřesnit. Typickým příkladem jsou stránky s nějakými formulářovými prvky. Data, která jsme do formuláře vyplnili, je nutné nějak dostat na server. Používají se 2 metody - metoda GET, ve které se data zakomponují přímo do adresy URL (www.server.com/stranka.aspx?parametr1=hodnota1¶metr2=hodnota2...), anebo se data odešlou metodou POST. Metodu GET zvládnete jistě sami, postup je úplně stejný, jediné, co musíte upravit, je právě adresa URL.
Pokud chceme odesílat formulář metodou POST, musíme ještě před odesláním požadavku poslat serveru příslušná data. Představme si, že v naší stránce máme tento formulář:
<p>Jméno: <input type="text" name="jmeno" /><br />
Příjmení: <input type="text" name="prijmeni" /><br />
<input type="submit" name="odeslat" value="Odeslat" />
Tato stránka vypadá nějak takto:
Pokud chceme odeslat vyplněný formulář na server (např. jméno Tomáš a příjmení Herceg), pak se uvnitř HTTP protokolu použije příkaz POST. Odpověď bude vypadat prakticky stejně, až na vrácená data, ale request bude začínat řádkem POST url HTTP/1.1 a po několika hlavičkách a dvojnásobném konci řádku budou následovat samotná data. Aby server věděl, kdy už data končí, měli bychom mu poslat též hlavičku Content-Length: počet bajtů. Data budou vypadat takto:
jmeno=Tomáš&prijmeni=Herceg&odeslat=Odeslat
Pokud hodnoty obsahují nějaké speciální znaky, měly by se prohnat funkcí URLEncode, kterou najdete v knihovně System.Web (musíte ji ve vlastnostech projektu na záložce References přidat do seznamu). Tato metoda je k nalezení uvnitř třídy HttpUtility. Jak tedy odeslat tento fiktivní formulář, to vidíme zde:
'vystavit požadavek na dnešní den
Dim rq As HttpWebRequest = HttpWebRequest.Create("http://www.server.com/")
'sestavit data
Dim data As String = "jmeno=" & Web.HttpUtility.UrlEncode("Tomáš") & "&prijmeni=Herceg&odeslat=Odeslat"
Dim bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(data) 'převést data na bajty v kódování ASCII
'poslat je na server
rq.Method = "POST" 'nastavit metodu POST
rq.ContentLength = bytes.Length 'nastavit Content-Length na počet bajtů dat
rq.GetRequestStream().Write(bytes, 0, bytes.Length) 'zapsat data do streamu
'odeslat jej na server a získat odpověď
Dim rs As HttpWebResponse = rq.GetResponse()
'dále s odpovědí pracujeme jako normálně
Data formuláře, která si sestavíme, zapíšeme metodou Write do streamu požadavku, který získáme metodou GetRequestStream. Musíme nastavit vlastnost ContentLength na počet bajtů a také vlastnost Method na hodnotu POST, jinak nám objekt HttpWebRequest ani nedovolí do streamu zapisovat. Pak již jen získáme odpověď a pracujeme s ní jako obvykle.
Závěrem
Tento článek je jenom krátkým vhledem do problematiky. Je dobré vědět, jak protokol HTTP funguje uvnitř, ale je zbytečné psát funkcionalitu HTTP vždy od píky. Lepší je použít standardní řešení, které nám aplikaci neudělá složitou a nepřehlednou.