Per aspera ad astra. Také AJAX měl v cestě na výsluní řadu překážek. Sotvaže se vyřešily trable s nepodporou a inkompatibilitou a ajaxové prezentace se mohly začít rozmáhat, objevil se problém s adresováním obsahu – nedal se vytvořit odkaz na jiný než výchozí obsah a stejně tak bylo nemožné dostat se použitím tlačítka Zpět k předchozímu obsahu.

Ačkoliv se totiž obsah měnil, stále se jednalo o tutéž stránku s toutéž adresou! Ale i tyto potíže se podařilo promptně vyřešit. A to tím, že se každému určitému obsahu přiděluje tzv. kotva. Díky ní má každý takový obsah svou adresu (např.: http://priklad.cz/ajax.html#cast3), na níž lze odkázat a podle té části adresy za mřížkou se pak vygeneruje žádaný obsah. Zároveň se tím zadělalo na opačný problém – pokud ajaxová aplikace příliš svižně střídá adresovaný obsah, zaplňuje historii prohlížeče balastem.

Bylo však potřeba se vypořádat ještě s dalšími obtížemi, jejichž řešení už bylo podstatně složitější.

Přečtěte si také první díl seriálu o komunikaci mezi serverem a prohlížečem: Ajax v kostce.

Cross-origin requests

Působnost AJAXu je omezena zásadou stejného původu (the same origin policy) – to znamená, že stránka musí pocházet ze stejného serveru, z něhož čerpá data, rovněž port a protokol musí být stejné. Ve většině případů tato restrikce nepředstavuje žádný problém. Někdy ale účel aplikace vyžaduje, aby stránka byla schopna komunikovat s vícero servery (resp. vícero službami na různých doménách), čemuž toto omezení brání. Dá se to nějak obejít? Ano, stránka může komunikovat sice jen s jedním serverem, ale ten se všemi dalšími. Přes tento jeden server se tedy aplikace může dostat ke zdrojům dat z ostatních serverů. Existuje však i přímé řešení – má zkratku CORS (Cross-Origin Resource Shared). Jedná se v podstatě o doplňkový protokol, který upravuje současná pravidla komunikace mezi prohlížečem a serverem takovým způsobem, aby ajaxovým aplikacím byly zpřístupněny i zdroje dat mimo domácí doménu.

Klíčovou roli při tom hrají předběžné požadavky (preflight requests) typu OPTIONS, obsahující níže uvedené zvláštní hlavičky. Prohlížeč se jimi nejprve ptá serveru, zda je ochoten dané aplikaci data poskytnout, a na základě odpovědi ze serveru se pak rozhodne, zda si je opravdu vyžádá.

Hlavičky posílané prohlížečem serveru:

  • Origin – obsahuje tzv. origin (česky původ) sestávající z protokolu, domény a čísla portu (implicitně 80);
    příklad: Origin: http://www.interval.cz
  • Access-Control-Request-Method – udává typ chystaného požadavku;
    příklad: Access-Control-Request-Method: GET
  • Access-Control-Request-Headers – vyjmenovává hlavičky, které budou v požadavku použity: Accept, Accept-Language atd. Jednotlivé názvy hlaviček se od sebe oddělují čárkou.

Hlavičky odesílané serverem jako odpověď prohlížeči:

  • Access-Control-Allow-Origin – může obsahovat:
    • seznam povolených originů, jednotlivé originy se oddělují mezerou
    • hvězdičku (*), která značí, že všechny originy jsou povoleny; tuto hodnotu však nelze použít společně s Access-Control-Allow-Credentials: true
    • null
  • Access-Control-Allow-Methods – obsahuje seznam povolených typů požadavku;
    příklad: Access-Control-Allow-Methods: GET, POST
  • Access-Control-Allow-Headers – obsahuje seznam povolených hlaviček
  • Access-Control-Allow-Credentials – oznamuje, jestli součástí požadavku mohou být cookies, HTTP autentizace a client-side certifikáty;
    příklad: Access-Control-Allow-Credentials: true
  • Access-Control-Max-Age – udává v sekundách dobu, po kterou je předběžný požadavek uchován v cache;
    příklad: Access-Control-Max-Age: 864000
  • Access-Control-Expose-Headers – informuje, které hlavičky je bezpečné poslat

Aby bylo vysávání datových zdrojů z dalších domén proveditelné AJAXem, musí být splněny dva předpoklady: prohlížeč, ve kterém aplikace běží, musí podporovat CORS a musí být zajištěno, aby kontaktovaný server, kladně vyřídil předběžný požadavek. Problém je, že IE podporuje standardní CORS až od desáté verze, takže jeho nižším verzím je nutné podstrčit nestandardní objekt XDomainRequest. Provede se to takto:

function createCORSRequest(method, url){
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr){
    xhr.open(method, url, true);
  } else if (typeof XDomainRequest != "undefined"){
    xhr = new XDomainRequest();
    xhr.open(method, url);
  }
  return xhr;
}

var req = createCORSRequest("GET", "http://databanka.eu/resource.php");
req.onload = function(){
  // kód pro zpracování došlých dat
};
req.send();

Když prohlížeč podle URL zjistí, že se aplikace dožaduje dat z cizího zdroje, vyšle nejprve k danému serveru předběžný požadavek ve tvaru

OPTIONS /resource.php HTTP/1.1
Host: databanka.eu
Access-Control-Request-Method: GET
Origin: http://domena.cz

Pak záleží na tom, jestli je zdroj přístupný CORS. Autoři by si především měli hlídat, aby takto nezpřístupnili žádnou stránku s přihlašovacím formulářem, tím by nahrávali útokům typu CSRF (Cross-Site Request Forgery). Vyřizování předběžných požadavků by mělo být naprogramováno s jistou dávkou opatrnosti. Nedoporučuje se například dovolit požadavky typu DELETE a PUT.

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
  if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Credentials: false');
    header('Access-Control-Max-Age: 86400');
  }
  exit();
}

Dále již komunikace probíhá obvyklým způsobem.

Pouze jednosměrná komunikace

Proces výměny dat prostřednictvím AJAXu je postaven na známém principu: klient zašle požadavek, server odpoví. Nejprve tedy prohlížeč inicializuje připojení přes TCP (Transmission Control Protocol) k serveru. TCP spáruje koncové IP adresy a porty a dále mezi nimi zajišťuje bezchybný přenos dat. Jelikož však informace nestačí jenom poslat, ale je třeba jim také porozumět, běží nad transportní vrstvou TCP ještě aplikační protokol HTTP. A zde je Achillova pata AJAXu. HTTP neumožňuje serveru zasílat nevyžádané informace. AJAX byl přizpůsoben protokolu HTTP, což v případě takových aplikací jako chat nebo hry po síti výrazně znesnadňuje komunikaci, která kvůli tomu nesmí vybočit ze schématu požadavek → odpověď.

Udržet za těchto okolností živou komunikaci lze použitím triku zvaného polling. Ten spočívá v tom, že se klientská část aplikace opakovaně v zadaném intervalu táže serveru, co je nového. Pro každý takový dotaz je inicializováno nové spojení, které server automaticky ukončí po zaslání odpovědi. Elegantní řešení to jistě není, při krátce nastaveném intervalu zatěžuje síť častou inicializací TCP spojení, proto existuje ještě druhá varianta – long polling. Server udržuje spojení delší dobu a přitom kontroluje databázi. Jakmile v ní objeví čerstvá data, okamžitě je expeduje jako odpověď a ukončí spojení. Ze strany klienta je po obdržení odpovědi hned navázáno nové. Tento způsob uspokojivě zkracuje prodlevu mezi vznikem a odesláním nových dat, nezahlcuje síťový provoz, jenže více zaměstnává server.

V úvahu připadá také streaming. Je výhodný tím, že negeneruje nadbytečný síťový provoz. Server totiž posílá postupně všechny odpovědi v rámci jediného spojení.

function getMessage() {
  ...
  return $msg;
}

while (true) {
  print getMessage(); //vypíše novou zprávu
  flush(); //odešle výpis
  sleep(2); //pauza v cyklu
}

Na straně klienta tedy stačí objektem XMLHttpRequest inicializovat jedno (lze paralelně i více) připojení a pak soustavně zpracovávat přibývající hodnotu vlastnosti response.

function newMessage() {
  var messages = xhr.response;
  if (messages.length > pos) {
    var msg = messages.substring(pos, messages.length);
    document.getElementById("zprava").innerHTML = msg;
    pos = messages.length;
  }
}

var xhr = new XMLHttpRequest();
xhr.open("GET", "messages.php", true);
xhr.send(null);
var pos = xhr.response.length;
setInterval(newMessage, 2000);

Ačkoliv je tohle řešení technicky zajímavé, pro obyčejný server je nemyslitelné. Udržovat co nejdelší spojení není účelné. Server jako Apache může současně zvládat jen omezený počet spojení. Dlouhým vyřizováním dříve došlých požadavků by tak blokoval ty pozdější. Toto omezení lze překonat pomocí tzv. comet serveru (Comet je workaround, který zajišťujě komunikaci v reálném čase). Dalším zdrojem problémů jsou firewally a proxy servery. Například optimalizace nějakého proxy serveru může automaticky přetínat spojení, která běží delší čas.

AJAX za zenitem

Je zřejmé, že AJAX není koncipován pro obousměrnou komunikaci. Jeho použití k tomuto účelu je značně složité a dost nepraktické. Bylo potřeba vyvinout něco nového, co by nahradilo dřívější nevyhovující postupy. Ale o tom zase příště.

Celý seriál najdete pod štítkem: komunikace mezi stránkou a serverem

Žádný příspěvek v diskuzi

Odpovědět