ugrás a tartalomhoz

Cross-domain kérések a háttérben JSONP segítségével

kacsandiz · 2012. Jan. 25. (Sze), 21.02

Manapság az egyre összetettebb weboldalak, webalkalmazások fejlesztésekor gyakran merül fel az igény, hogy kérést küldjünk a háttérben egy másik domainre, aldomainre. Eme próbálkozásainkat a böngészők (biztonsági okokra hivatkozva) nem nagyon díjazzák, de szerencsére van egy lehetőségünk a kivitelezésre, a JSONP, erről fogunk most beszélni.

A probléma: same origin domain policy

A Netscape Navigator 2.0 óta létezik egy same origin domain policy nevű koncepció, melynek értelmében a böngésző oldali programozási nyelvek nem érhetnek el más domaineken lévő erőforrásokat. Az alábbi táblázat tartalmazza, hogy ennek értelmében mit lehet és mit nem. Legyen a kiindulási URL-ünk a http://xy.hu/index.php, ennek az oldalnak a JavaScript kódjából próbálunk meg most (elméletben) kéréseket küldeni a következő URL-ekre:

URL Magyarázat Eredmény
http://xy.hu/test.php Ugyanaz a hoszt és port Sikeres
http://xy.hu/dir/test.php Ugyanaz a hoszt és port Sikeres
http://xy.hu:3000/test.php Ugyanaz a hoszt, másik port Sikertelen
https://xy.hu/test.php Másik protokoll Sikertelen
http://www.xy.hu/test.php Másik hoszt Sikertelen

Ebből láthatjuk, hogy böngészők csak az ugyanarra a domainre, ugyanazzal a protokollal és ugyanarra a portra küldött kéréseket támogatják. Kivételt jelent ez alól a HTML <script> elem, a JSONP ezt használja ki.

A megoldás: JSONP

Elmélet

A JSONP jelentése „JSON with Padding”. Nagyon egyszerű, mégis nagyon hasznos technika, a lényege, hogy írunk egy callback függvényt, majd a JavaScript kódunkból dinamikusan beszúrunk egy script taget, amely egy másik szerveren helyet foglaló JavaScriptre fog hivatkozni. A másik szerveren valójában egy valamilyen szerveroldali programozási nyelvben írt program várakozik, amelynek a kimenete egy egyszerű JavaScript függvényhívás (a callback függvényünké) lesz, a paraméterében egy JSON sztringgel, amely a továbbítandó adatokat tartalmazza. Így a végén a callback függvényünk paraméterében megkapjuk a kívánt adato(ka)t. Szeretném itt megjegyezni, hogy bár a kérés a háttérben történik, akár az AJAX kérések esetében, de a kettőnek nem sok köze van egymáshoz, tehát a JSONP nem AJAX. A későbbiekben láthatjuk, hogy sokkal nehezebb felügyelni, kontrollálni mint egy AJAX kérést. Most pedig lássuk működés közben.

Gyakorlat

Tegyük fel, hogy van egy oldalunk amely a http://xy.hu/index.php URL-en érhető el. Innen szeretnénk egy kérést indítani JavaScriptből a http://example.com/test.php irányába. Először is definiáljuk a callback függvényünket, a neve legyen parseRequest():


function parseRequest(response) {
    console.log(response);
}

Ez annyit csinál most, hogy a kapott response paramétert kiírja a konzolra. A következő lépés a script tagünk beillesztése, amely a http://example.com/test.php-re mutat:


var url = "http://example.com/test.php?callback=parseRequest";

var script = document.createElement("script");
script.setAttribute("src", url);

document.getElementsByTagName("head")[0].appendChild(script);

Látható, hogy átadjuk a callback paraméterben a callback függvényünk nevét, erre a függvényhívás miatt lesz szükségünk. Végül a http://example.com/test.php címen lévő PHP szkriptünk:


<?php	
$callback = $_GET['callback'];
echo $callback . '({"data": "Hello"});'; // parseRequest({"data": "Hello"});

Ennek a kimenete egy JavaScript kód lesz, amely meghívja a parseRequest() nevezetű függvényünket, a paraméterében átadva neki a kívánt adatokat, természetesen JSON-ban.

Korlátok

Korábban említettem, hogy a JSONP nem AJAX, tulajdonképpen egy szimpla kérés a szerver felé. Ebből fakadóan van több korlátja is, többek között, hogy nem tudjuk a kérést megszakítani, nem kapunk értesítést az aktuális állapotáról, és az időtúllépések kezelése is sokkal nehezebb. Ez főleg akkor okozhat gondot, ha long polling technikával szeretnénk kombinálni, érdemes ilyenkor úgy megírni a szerveroldali alkalmazásunkat, hogy a timeout felé közeledve a szerver küldjön valamilyen adatot, ezzel lezárva a kapcsolatot, mert a timeoutról már nem fogunk értesülni kliens oldalon, mint például AJAX esetében. További hátránya, hogy POST kérések küldésére nem alkalmas, így főleg olyan célokra ideális, ahol adatot kérünk le a szervertől, például support chat, valósidejű statisztikai alkalmazások stb.

Biztonság

Az egyik legnagyobb hátulütője a JSONP-nek a biztonság. Fontos tisztáznunk, hogy mennyire bízunk meg abban a domainben, amelyre a kérést küldjük, hiszen más JavaScript kódot is beinjektálhatnak az oldalunkba, ezzel pedig teljes hozzáférést nyerhetnek, átszabhatják azt, cookie-t lophatnak, átirányíthatják a felhasználót egy másik oldalra, vagy pedig bejelentkezett felhasználóktól lophatnak érzékeny adatokat. Vigyázni kell tehát, hogy minden harmadik féltől származó adatot az ördögtől valóként kezeljünk, és végezzük el a megfelelő ellenőrzéseket, mielőtt felhasználnánk azt.

 
kacsandiz arcképe
kacsandiz
Kacsándi Zsolt fő érdeklődési területei a webalkalmazások fejlesztése, fejlesztési folyamatok hatékonyságának növelése és a minőségbiztosítás. A fejlesztés mellett igyekszik képben lenni az üzemeltetéssel kapcsolatos kérdésekben, technológiákban is.
Szabadidejében szívesen publikál szakmai témákban, illetve segédkezik rendezvények szervezésében.
1

A modern böngészők már

tgr · 2012. Jan. 26. (Cs), 09.29
A modern böngészők már támogatják a cross-domain AJAX requesteket, csak ezeket szerveroldalon valamivel bonyolultabb kezelni.
2

+1, már évek óta van

inf3rno · 2012. Jan. 26. (Cs), 10.15
+1, már évek óta van cross-domain ajax... Mondjuk ettől függetlenül pl a google analytics is script taget használ. Gondolom egyszerűbb így megoldani... Ettől függetlenül jó a cikk, csak így tovább! Folytatásnak írhatnál a cross-domain ajax-ról :-)
3

Így van, viszont nekem egy

kacsandiz · 2012. Jan. 26. (Cs), 10.42
Így van, viszont nekem egy konkrét feladat kapcsán volt szükségem erre, és olyan megoldást kerestem, ami a régebbi (esetleg mobil) böngészők esetén is működőképes. :)
Itt van egy táblázat a CORS támogatottságáról: http://caniuse.com/cors
Feltételezem, hogy a Google is azért használja ezt az Analytics-nél, hogy a régebbi böngészőket ne zárja ki a statisztikákból.
4

Hjah, 55%-nál van support

inf3rno · 2012. Jan. 26. (Cs), 10.53
Hjah, 55%-nál van support eszerint.
6

85%-nál (a mobilokat nem

tgr · 2012. Jan. 26. (Cs), 22.39
85%-nál (a mobilokat nem számítva 99-nél, csak az Opera hiányzik, az se sokáig). Az IE 8-9 nem tud sütiket küldeni CORS requestben, de olyat nem tudsz JSONP-vel sem.
8

nem tud sütiket küldeni CORS

Poetro · 2012. Jan. 26. (Cs), 23.23
nem tud sütiket küldeni CORS requestben

Én ennek azért örülök. Utálom, ha minden harmadik fél mindenféle hulladékkal látja el a HTTP kéréseket. Ha már CORS, akkor az legyen szép tiszta HTTP mindenfajta sütimorzsa nélkül. Ma már szinte minden szolgáltatás tud valami kulcs alapú egyeztetést. Azaz egyszer autentikál, kap egy kulcsot, és azután azt hozzárakod mindig az URL-hez. Legalább, ha nem akarom, akkor a Facebook szerű szörny nem követ a világ végére is.
9

Egyrészt harmadik fél soha

tgr · 2012. Jan. 27. (P), 09.38
Egyrészt harmadik fél soha nem tud sütit hozzáadni egy HTTP requesthez (ez az egész same-domain biztonsági architektúra alapja, a requestbe mindig csak a céldomain által beállított sütik kerülnek be), másrészt a sütik hozzáadása CORS-ban nem automatikus (sőt eléggé agyon van bonyolítva: először küldeni kell egy OPTIONS requestet, amire válaszul a szerver engedélyezi a sütiküldést - szerencsére a böngészők automatikusan megteszik ezt, úgyhogy csak szerveroldalon van szívás vele), tehát még csak attól sem kell tartani, hogy automatikusan növekedik a request mérete, ha nincs szükség rá. Harmadrészt meg a session ID-t URL-be tenni visszalépés a kőkorba: az URL-t a küldő állítja, a sütit meg a böngésző, tehát ha URL-ben kell autentikálni, akkor explicit azonosítás kell minden alkalommal, márpedig a sütiket pont ennek elkerülésére találták ki.
17

IE 8-9-nek nulla a CORS

inf3rno · 2013. Május. 1. (Sze), 20.55
IE 8-9-nek nulla a CORS támogatása. Van egy XDomainRequest nevű hányás benne, amiről valami kattant az hitte, hogy jó ötlet. Nem tudom ilyenekt ms-nél miért engednek vezető pozícióba bármilyen projektnél... A böngészőkkel évek óta ezt játszák, körülbelül a 10. olyan hiba, amit a fejlesztők szándékosan okoztak, és ami használhatatlanná teszi a böngészőt. Na de ennyit mára az IE szidásról, én IE10+ fejlesztek CORS-os alkalmazást, aztán ezzel letudtam a problémát...

Nem is emlékszem erre a cikkre, újra el fogom olvasni.
5

Az Analytics nem ezt

tgr · 2012. Jan. 26. (Cs), 22.37
Az Analytics nem ezt használja, hanem képek src-jébe teszi bele az elküldendő URL-t. Persze nekik könnyű, mert a scriptnek nincs szüksége a szerver válaszára.
7

Milyen képekről van szó? :D

inf3rno · 2012. Jan. 26. (Cs), 22.39
Milyen képekről van szó? :D Amit eddig én láttam, az script src-s megoldás volt, de lehet, hogy van többféle is... :-)
10

Képek

T.G · 2012. Jan. 27. (P), 15.55
http://www.google-analytics.com/urchin.js
Valahol a kód közepén van, hogy:

var i=new Image(1,1);
i.src=_ugifpath+"?"+"utmwv="+_uwv+s;
Ilyen az amikor üzenünk, de a válasz már nem érdekel. :)
12

Ilyen van az egyik oldalon,

inf3rno · 2012. Jan. 27. (P), 17.24
Ilyen van az egyik oldalon, amit próbálok használható formára hozni. Lehet, hogy valami régebbi verzió, kíváncsi lennék működik e egyáltalán...

<script type="text/javascript">
	var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
	document.write(unescape("%3Cscript src=\'" + gaJsHost + "google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
	try {
		var pageTracker = _gat._getTracker("UA-00000000-1");
		pageTracker._trackPageview();
	} catch(err) {}
</script>
14

Ez magát a GA javascript

tgr · 2012. Jan. 27. (P), 19.26
Ez magát a GA javascript kódot tölti be, csak annyiban különbözik egy mezei script src=... tagttől, hogy az aktuális protokollt használja.

Egyébként tényleg régi, mostanában már DOM manipulációt használnak, mert az nem blokkol töltődés közben.
13

Urchin

Poetro · 2012. Jan. 27. (P), 18.31
Az Urchin elég régi, sőt, ahogy olvastam, hamarosan meg is fog szűnni.
11

var

kacsandiz · 2012. Jan. 27. (P), 17.12
var sc=document.createElement('script');
sc.type='text/javascript';
sc.id="_gasojs";
sc.src='https://'+d+'/analytics/reporting/overlay_js?gaso='+_utk+'&'+Math.random();
document.getElementsByTagName('head')[0].appendChild(sc);


Ez pedig a kód végén van. :)

Szerk.: T.G 15.55-ös hozzászólására akart válasz lenni.
15

Ez meg a site overlay,

tgr · 2012. Jan. 27. (P), 19.37
Ez meg a site overlay, szintén nem üzenetküldésre szolgál.
16

Az Errorception blogján most

tgr · 2012. Jan. 30. (H), 21.24
Az Errorception blogján most írtak egy jó összefoglalót a különböző cross-domain javascript megoldások előnyeiről és hátrányairól.