A fejezet a szerveroldalon történő dinamikus webprogramozás alapkoncepcióival ismerteti meg az olvasót. Ennek lényege, hogy egy szerveroldali programnak kell a kimenetet előállítania. E mellett megismerkedünk a kliens és szerver, illetve szerveroldali program kapcsolatát biztosító protokollokkal, a HTTP és CGI protokollal is.
Tananyagunkban eddig a kliensoldali dinamikus webprogramozás alapjairól volt szó. Noha a tartalom (HTML dokumentum, CSS és JavaScript állományok, képek) böngészőbe töltése kliens-szerver architektúrán keresztül valósult meg, eddig a szerverrel nem is nagyon kellett foglalkoznunk. Egyelőre az volt a fontos, hogy a megfelelő HTML és JavaScript állomány a böngészőbe töltődjön, ahol aztán a JavaScript programkód segítségével a dokumentum interaktívvá, dinamikussá tehető. Ehhez – ad absurdum – szerver sem szükséges, kliensoldali fejlesztés akár lokális fájlkiszolgálással is elvégezhető, hiszen minden programozás a böngészőben történik.
Amikor szerveroldalon szeretnénk dinamikus weboldalakat előállítani, akkor a kliens-szerver architektúra mindkét komponensére szükség van. A kliensre azért, mert ő kezdeményezi a kérést, és általában ő jeleníti meg az eredményt. A szerverre azért, mert ebben az esetben a weboldalak előállítása itt valósul meg programozottan. Az architektúra tehát szükségképpen bonyolultabb, mint a kliensoldali webprogramozásnál. Míg ott a szerverről elfeledkezhettünk, és elég volt csak egy komponensre, a kliensre koncentrálni, addig mostantól két komponensre kell figyelnünk, és együttműködésüket biztosítanunk. A bonyolultságot csak fokozza, hogy a két komponens térben és időben is elválik egymástól. Térben azért, mert általában a kliens és a szerver nem egy gépen helyezkedik el (kivéve a fejlesztői gépek), időben pedig azért, mert a vezérlés nem egyidejű: a folyamatot a kliens kezdeményezi, majd a szerver dolgozik, végül megint a kliensél van a képzeletbeli labda, ahogy azt a következő ábra is mutatja.
A szerveroldali webprogramozás komplexitását ráadásul az is növeli, hogy fontos ismernünk a kliens-szerver kommunikációját biztosító csatorna jellegzetességeit is. A két komponens közötti kommunikáció az internetes hálózaton valósul meg, a kommunikáció rendjét pedig a HTTP protokoll biztosítja.
Ha a szerveroldalra tekintünk, akkor a legelső fejezetben már megnéztük a statikus és dinamikus oldalak közötti különbséget. Láthattuk, hogy statikus állományok kiszolgálása gyakorlatilag nem más, mint egyszerű fájlkiszolgálás. A kérés pillanatában a szerveren már létezik a leküldendő tartalom. A szervernek nem kell mást tennie, mint a megadott könyvtárból felolvassa a kívánt tartalmat, és leküldi a kliensnek. Annak eldöntése, hogy mely állományok statikusak, a webszerver konfigurációja alapján történik. Ez szerverenként eltérő lehet, de általában a fájl helye vagy kiterjesztése határozza meg azt, hogy a webszerver mit csinál az adott állománnyal. A tipikus kiterjesztések esetén (.html, .jpg, .png, .gif, .css, .js) az állományok tartalma minden feldolgozás nélkül visszaküldésre kerül.
Más a helyzet szerveroldali dinamikus webprogramozás esetében. Ekkor a kért (1) állomány a kikeresés után nem kerül egyszerűen visszaküldésre, hanem a webszerver egy megfelelő programnak adja át a vezérlést (2). Ez a program állítja elő a tartalmat, adja át a webszervernek (3), amely továbbküldi azt a kliensnek (4).
A program indításának szabályait a Common Gateway Interface (CGI) szabvány határozza meg. Ez egy olyan protokoll, amely leírja, hogy egy webszerver hogyan indíthat el egy programot és milyen módon kommunikál vele. A webszerver a kért állomány útvonala vagy kiterjesztése alapján dönt arról, kell-e a kikeresés után bármit is csinálni, és ha igen, akkor melyik programot kell elindítania. A futtatható bináris állományok tipikusan egy cgi-bin nevű könyvtárban helyezkednek el, de az is lehet, hogy kiterjesztés alapján dönt a webszerver a fájl futtatásáról (.cgi, .php).
A webszerver által futtatott programra nincsenek megszorítások. Ez lehet egy bináris állomány, amely valamilyen általános célú programozási nyelv fordításakor keletkezik (pl. C++ vagy Pascal), de lehet valamilyen szkriptnyelven megírt program, amit a megfelelő értelmező hajt végre (Shell szkript, Perl, PHP, Python).
Összefoglalva, a szerveroldali webprogramozás tehát nem más, mint hogy a HTML kódot egy program segítségével állítjuk elő. Programunk helyes működését pedig úgy tudjuk ellenőrizni, hogy összehasonlítjuk a generált tartalmat, az elvárt tartalommal.
A fenti ábrán láthatjuk, hogy architektúránk három komponensűvé vált. Ahhoz, hogy megértsük, hogyan jut el a kérés a klienstől a programig, és onnan vissza, ahhoz meg kell vizsgálni azokat a protokollokat, amelyek az egyes komponensek közötti kommunikációt szabályozzák. Így a következőkben először azt nézzük meg, hogyan történik a kérés-válasz a HTTP protokollon keresztül, majd azt, hogy hogyan indít el a webszerver egy programot a CGI segítségével.
A HyperText Transfer Protocol (HTTP) egy kérés-válasz alapú protokoll a kliens és a szerver között. A protokollt a világháló működéséhez dolgozták ki (a HTML és URI mellett), ezen keresztül történik a webszervereken közzétett dokumentumok elérése. E protokoll szerint a kommunikációt mindig a kliens kezdeményezi egy kérés formájában, amit elküld az interneten keresztül a webszerver 80-as TCP portjára. A szerver a kérést feldolgozva megfelelő formátumban válaszol, és visszaküldi azt a kliensnek. A HTTP a TCP/IP feletti protokoll. A webes világban a kliens tipikusan egy böngésző, a szerver pedig valamilyen webszerver, amely képes értelmezni a HTTP kéréseket.
A HTTP egy szabványos protokoll, karbantartása többek között a WWW Consortium felügyelete alá tartozik. A szabványt ún. RFC-k (angol Request For Comments) formájában teszik elérhetővé. Az első verzió még 1991-ben jelent meg 0.9-es verziószámmal, az aktuális az 1999-ben véglegesített 1.1-es verzió.
A HTTP kérés egy meghatározott szerkezetű szöveges információ. Első sora kérés módját (METÓDUS), a kért tartalmat (ERŐFORRÁS) és a HTTP verziószámát (VERZIÓ) tartalmazza. Ezt követi tetszőleges számú fejlécsor FEJLÉC: ÉRTÉK formában, majd egy kötelező üres sort követően opcionális üzenettest következhet (ÜZENETTEST). Az általános formátum tehát a következő:
A legegyszerűbb kérésben az első soron kívül legalább azt a helyet meg kell adni (Host), ahonnan az erőforrás lekérdendő:
Ebben az esetben a webprogramozas.inf.elte.hu szerverről kérjük le az index.html állományt. A kérés végét az üres sor jelzi.
A valóságban a kéréseket nem kézzel állítjuk össze, hanem ezt a feladatot a böngésző végzi el. Az így előállt kérések természetesen bonyolultabbak lehetnek a fentinél, hiszen számos egyéb, a szerverrel közlendő információt is tartalmaznak fejlécként. A webprogramozas.inf.elte.hu szerver kezdőoldalát például egy böngésző így kéri le:
Ebben megjelennek a böngészőre vonatkozó információk is (User-Agent), a válaszban várt formátumok, nyelvek és kódolások (Accept, Accept-Language, Accept-Encoding), a felküldött sütik (Cookie, ld. még a megfelelő JavaScriptes fejezetet) és a kliens-szerver közötti kapcsolat jellegét (Connection).
A kérés első sorában lévő metódus a megadott erőforráson végzendő műveletet határozza meg. A szabvány nyolcféle metódust definiál, ezek a következők:
A fentiek közül a HEAD, GET, OPTIONS és TRACE metódusokat biztonságos metódusoknak is hívják. Biztonságosak olyan szempontból, hogy csak információ lekérésére szolgálnak, és nem változtatják meg a szerver állapotát. Ezekkel együtt a PUT és a DELETE idempotens is, azaz a többszöri végrehajtás ugyanazt eredményezi, mint az egyszeri. Ezeknél a metódusoknál a kliens többször is újrapróbálkozhat további következmények nélkül.
Bár a HTTP szabvány által megadott metódusok sokféle művelet elvégzését teszik lehetővé, látni fogjuk, hogy a böngészők ezek közül csak a GET és POST műveleteket használják. (Az AJAX-ról szóló fejezetben látjuk majd, hogy JavaScripttel mindegyik metódus elérhető.)
Ahogy a kérés, úgy a HTTP válasz is egy meghatározott formátumú szöveges információ. Első sora, a státuszsor tartalmazza a protokoll verzióját (VERZIÓ), a válasz státuszkódját (STÁTUSZKÓD) és egy szöveges indoklást (INDOKLÁS). Ezt megint tetszőleges számú fejlécsor követi, majd egy kötelező üres sor után megint az opcionális üzenettest következik (ÜZENETTEST).
Az előző fejezetbeli kérésre például a szerver a következőképpen válaszol:
Fejlécként itt is sokféle adat jelenik meg: a kiszolgálás időpontja (Date), a szerver típusa és verziója (Server), a küldött tartalom típusa (Content-Type) és mérete bájtban (Content-Length). Az üzenettestben érkezik maga a HTML tartalom.
A státuszsorban lévő státuszkód jelzi a kiszolgálás sikerességét. A státuszkód egy háromjegyű szám, kezdő betűje határozza meg az egyes nagyobb csoportokat:
Források:
Az alábbiakban tekintsünk át néhány olyan eszközt, amellyel kipróbálható vagy nyomon követhető a HTTP kommunikáció.
A telnet egy TCP alapú, kétirányú, általánosan elérhető, nyolcbites byte-alapú kommunikációs protokoll. Segítségével kérést tudunk vele küldeni tetszőleges TCP portra. A telnet parancs megadását követően a kérés szövegét egyszerűen beírjuk vagy bemásoljuk a konzolba. A kérés végeztével ugyancsak a parancssori ablakban láthatjuk a szerver válaszát.
A HTTP kommunikációt a böngészők elrejtik a mindennapi felhasználók szemei elől. Fejlesztéskor azonban néha hasznos lehet látni a HTML oldalak viselkedését is meghatározó HTTP fejléceket. Ezt segítik elő a különböző kiegészítők a böngészőkhöz. Mozilla Firefox alatt például az alábbi kiegészítők hasznosak a HTTP üzenetek vizsgálata szempontjából:
Míg a HTTP protokoll a kliens és a szerver kommunikációját szabályozza, a Common Gateway Interface, röviden CGI, azt határozza meg, hogy egy webszerver hogyan indíthat el egy programot és milyen módon cserélnek adatot egymással. A program indítása egyszerűen úgy történik, hogy a webszerver az erőforrásként megjelölt állományt binárisnak tekinti, és elkezdi futtatni.
Sokkal fontosabb azonban egy szkript számára, hogy hogyan kapja meg a szervertől a HTTP kéréshez tartozó információkat. Ezek négy helyen érkezhetnek a HTTP kérésben:
A HTTP üzenettestben érkező adatokon kívül a szerver minden egyéb információt környezeti változókban tesz elérhetővé a szkript számára.
A kérés körülményeihez például az alábbi környezeti változók jönnek létre:
Egy URL általános felépítése a következő:
Az URL egyes részei a következő táblázat szerinti környezeti változókban jelennek meg:
URL része | Környezeti változó |
scheme | SERVER_PROTOCOL |
host | SERVER_NAME |
port | SERVER_PORT |
path | SCRIPT_NAME |
query | QUERY_STRING |
Ezek közül érdemes kiemelni a <query> részt, ugyanis az URL-nek ez az egyetlen olyan része, amelynek tartalmát a felhasználó határozza meg.
A HTTP fejléceket a szerver automatikusan HTTP_ kezdettel látja el és teszi elérhetővé ilyen nevű környezeti változókban. Például az Accept fejléc értéke a HTTP_ACCEPT környezeti változóban érhető el.
Végül a HTTP kérés üzenettörzsében érkező adatokat a webszerver a futtatandó szkript standard bemenetén teszi elérhetővé. Az adatmennyiség hosszát a CONTENT_LENGTH környezeti változó adja meg. Az üzenettörzsben érkező adatot megint csak a felhasználó határozza meg.
A szkript feladata, hogy dinamikusan állítsa elő a válasz tartalmát. A szkript a kimeneti tartalmat a standard kimenetére írja, innen olvassa ki a webszerver, és küldi tovább a kliens felé.
A következő példa a szerver által létrehozott és a szkript által elérhető környezeti változókat sorolja fel:
Összefoglalva, a webszerver a szkriptnek információkat környezeti változókban és a standard bemeneten ad át, a generált tartalmat pedig a szkript standard kimenetén várja. Az adatok legtöbbje a HTTP kérés körülményeihez kapcsolódik, felhasználói adat egyedül a QUERY_STRING környezeti változóban és a standard bemeneten várható.
Most, hogy megismertük a három komponens, a kliens-szerver-program kommunikációs szabályait, nézzük meg ezek alapján, hogyan is néz ki egy ilyen program. A szerveroldali webprogramozást bevezető részben már említettük, hogy tulajdonképpen nincsen megkötés a bináris állomány programozási nyelvét illetően. A program egyetlen célja az az, hogy a HTTP és CGI protokollok betartása mellett webes tartalmat, elsősorban HTML állományt állítson elő. Így akár fordulhatunk egy közismert, a JavaScript nyelvi részét is bevezető nyelvhez, a C++-hoz.
Egy CGI programnak alapvetően háromféle művelet fontos:
C++-ban ezek az utasítások adottak. Környezeti változót a cstdlib könyvtár getenv függvényével tudunk lekérdezni, a standard input és output pedig többek között a cin és cout objektumokon keresztül valósítható meg.
Példaként nézzünk egy olyan C++ programot, amely egy szabványos HTML oldalt készít Hello világ szöveggel. A leküldendő végső tartalomnak a HTTP válasz formátumához kell igazodnia. Ez a webszerver által generált válaszkezdet és a rögtön mögé írt programkimenetből áll. A válaszkezdetben a státuszsoron kívül néhány fejléc jelenik meg. A programnak a HTML kimenet előtt tehát legalább egy, a fejléceket a tartalomtól elválasztó üres sort kell tartalmaznia, de érdemes megadni a tartalom típusát meghatározó Content-Type fejlécet is, hogy a böngésző HTML-ként jelenítse meg a tartalmat.
#include <iostream> using namespace std; int main() { cout << "Content-Type: text/html" << endl; cout << endl; cout << "<!doctype html>" << endl; cout << "<html>" << endl; cout << " <head>" << endl; cout << " <meta charset=\"utf-8\">" << endl; cout << " <title></title>" << endl; cout << " </head>" << endl; cout << " <body>" << endl; cout << " <p>Hello vilag!</p>" << endl; cout << " </body>" << endl; cout << "</html>" << endl; return 0; }
A programot lefordítva és egyszerűen futtatva a következő kimenetet kapjuk:
Content-Type: text/html <!doctype html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <p>Hello vilag!</p> </body> </html>
A hello.exe fájlt a cgi-bin könyvtárba másolva most már böngészőből is futtathatjuk beírva az elérhetőségét.
A webszerver megkapva a kérést, látja, hogy a cgi-bin könyvtárból kérünk egy állományt, így azt a CGI interfészen keresztül futtatni kezdi. A kliensnek leküldött szöveg a webszerver válaszkezdetéből és a program kimenetéből áll, a kliens tehát a következő HTTP választ kapja:
HTTP/1.1 200 OK Date: Thu, 26 Sep 2013 12:23:54 GMT Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: text/html <!doctype html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <p>Hello vilag!</p> </body> </html>
A böngésző értelmezve ezt megjeleníti az oldalt. A program működését a generált forrás megtekintésével ellenőrizhetjük.
Bár a fenti kimenetet program állította elő, de ugyanezt megtehettük volna egyszerű statikus HTML állománnyal is. Program használatának akkor van értelme, ha valamilyen logika kell a HTML generálásához. Ilyen lehet például gyakran ismétlődő elemek előállítása, ahogy a következő példa mutatja:
int main() { cout << "Content-Type: text/html" << endl; cout << endl; cout << "<!doctype html>" << endl; cout << "<html>" << endl; cout << " <head>" << endl; cout << " <meta charset=\"utf-8\">" << endl; cout << " <title></title>" << endl; cout << " </head>" << endl; cout << " <body>" << endl; for (int i = 1; i<=10; i++) { cout << " <p>Hello vilag!</p>" << endl; } cout << " </body>" << endl; cout << "</html>" << endl; return 0; }
De igazán akkor hasznos a program használata, ha a generált oldal tartalmát külső paraméterek határozzák meg. A következőkben erre mutatunk példát, ahol egy HTML listát egy szöveges fájl tartalma alapján generálunk. Ha változik a fájl tartalma, változik a megjelenített oldal is.
cout << " <ul>" << endl; ifstream f("lista.txt"); while (!f.eof()) { string sor; getline(f, sor); cout << " <li>" << sor << "</li>" << endl; } f.close(); cout << " <ul>" << endl;
A szöveges fájl tartalma:
A megjelenített oldal és annak forrása pedig a következőképpen néz ki:
A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.