ASP.NET AJAX 4: JSONP, avagy a Same Origin Policy megkerülése

Aki foglalkozott már AJAX-szal az tudja, hogy privacy okokból az XMLHttpRequest objektummal végzett kérésekre alapértelmezés szerint a böngésző érvényesíti az ún. Same Origin Policy-t, ami azt biztosítja, hogy az AJAX-os kéréseink csak arra a szerverre irányulhassanak, ahonnan az oldal letöltődött. Ez gond, ha nyilvános szolgáltatásokat akarok JavaScriptből elérni, nem is csoda, hogy van szabványos módszer a megkerülésére.

Ha például egy oldal head részébe teszek egy script tag-et és annak src attribútumában egy másik szerverre mutató FQDN-es URL-t adok meg, akkor az adott szkript le tud töltődni a másik szerverről és annak a szervernek a kontextusában fog meghívódni – azaz gyakorlatilag nem vonatkozik rá a Same Origin Policy. A jó hír az, hogy ezt meg lehet tenni dinamikusan is, amikor egy másik szerverre akarok hívni, akkor röptében hozzáadok a DOM-hoz egy script elementet, beállítom a forrását és a böngésző le fogja tölteni onnan a kódot.

Ez már majdnem olyan, mint egy AJAX-os hívás, hiszen bármikor el tudom indítani és ki tudok küldeni vele egy GET-es kérést, az egyetlen probléma vele, hogy nem tudom meg, hogy a szkript mikor töltődött le, illetve azt sem, hogy mi lett a válasz a kérésemre. Ez pedig azért van, mert egy normális AJAX-os hívás visszatérési értéke a következő:

  valami_json_adat

Például a Delicious  JSON API-ját felhasználva lekérhetjük az utolsó 3 postot, ha meghívjuk ezt az URL-t: http://feeds.delicious.com/v2/json?count=2&plain

A válasz pedig ez:

  [{"u":"http:\/\/engineeredweb.com\/blog\/09\/12\/preloading-images-jquery-and-javascript",
"d":"Preloading Images with jQuery and JavaScript | Engineered Web",
"t":["jquery","images","preload","javascript","plugin","image","preloading","development","plugins","preloader"],
"dt":"2009-12-16T00:00:00Z"},
   {"u":"http:\/\/www.allfacebook.com\/2009\/12\/facebook-privacy-new\/",
"d":"10 New Privacy Settings Every Facebook User Should Know",
"t":["facebook","privacy","socialmedia","web2.0","howto","security","article","information","social","socialnetworking"],
"dt":"2009-12-16T00:00:00Z"}]

Látszik, hogy ez meztelen JSON, ami nem rossz, de visszatérési értékként használhatatlan, nem tudjuk hol és hogyan feldolgozni. Ha rá tudnánk venni a távoli szolgáltatást, hogy a visszatérési érték ilyen legyen:

  OnComplete( valami_json_adat );

akkor elég lenne a saját oldalunkon belül létrehoznunk az OnComplete nevű functiont, ami automatikusan meghívódna, mikor a távoli szkript letöltődik és paraméterként megkapná a hívás visszatérési értékét. Egészítsük ki az előbbi Delicious URL-t egy plusz paraméterrel: http://feeds.delicious.com/v2/json?count=2&plain&callback=OnComplete

És nézzük meg a választ:

  OnComplete([{"u":"http:\/\/engineeredweb.com\/blog\/09\/12\/preloading-images-jquery-and-javascript",
"d":"Preloading Images with jQuery and JavaScript | Engineered Web",
"t":["jquery","images","preload","javascript","plugin","image","preloading","development","plugins","preloader"],
"dt":"2009-12-16T00:00:00Z"},
              {"u":"http:\/\/www.allfacebook.com\/2009\/12\/facebook-privacy-new\/",
"d":"10 New Privacy Settings Every Facebook User Should Know",
"t":["facebook","privacy","socialmedia","web2.0","howto","security","article","information","social","socialnetworking"],
"dt":"2009-12-16T00:00:00Z"}])

Ez már jó nekünk! Látszik, hogy pontosan az előző JSON tömb jön vissza, csak éppen paraméterként átadva a mi OnComplete metódusunknak. A válasz elején tehát van hely egy függvény nevének, ezt hívják padding prefixnek, a szabványt pedig JSON with paddingnek, azaz JSONP-nek.

Ez annyira jól működő és bevált módszer, hogy nagyon sok JSON-os API támogatja mind szerver, mind pedig kliens oldalon. A bevált gyakorlat szerint általában elég a GET-es hívásunk végére egy callback (lehet más is) nevű query string paramétert tennünk és a válasz máris nem JSON, hanem JSONP lesz. Itt egy másik példa: http://twitter.com/status/user_timeline/scottgu.json?count=5&callback=Akarmi

Ami az ASP.NET 4-et illeti, a Microsoft Ajax Library teljes támogatást ad JSONP hívások küldéséhez, ráadásul teljesen transzparensen. A korábban bemutatott DataView-t közvetlenül köthetjük egy távoli URL-hez, automatikusan JSONP-t fog használni. A háttérben ugyanis a Sys.Net.WebServiceProxy osztály dolgozik, aminek van 2 idevágó paramétere:

  • enableJsonp: ha true, akkor a hívás JSONP-vel fog történni. Ezzel gyakorlatilag nem kell foglalkoznunk, mert az osztály automatikusan beállítja, ha nem a saját szerverünkre hívunk vissza.
  • jsonpCallbackParameter: ez annak a query string paraméternek a neve, amiben a távoli szolgáltatás a callback függvény nevét várja. Ennek az alapértéke callback, ha ez jó nekünk, nem kell állítgatnunk.

Ez esetek nagy részében ennek a két paraméternek az alapértelmezett működése megfelelő, tehát anélkül, hogy bármit csinálnánk, a hívás automatikusan JSONP-re vált.

A bétában a DataView-hoz kötés bugos és bár van hozzá workaround, inkább a kézi megoldást mutatom meg. A fenti JSON részletekből látszik, hogy a Delicious által visszaadott JSON milyen mezőket tartalmaz, ehhez létrehozhatunk egy DataView-t:

    <ul 
        id="dvResults" 
        class="sys-template" 
        sys:attach="dv">
        <li>
            <b>
                <a sys:href="{{ u }}">{{ d }}</a>
            </b>
            <br /> 
            {{ dt }} 
        </li>
    </ul>

Létrehozhatunk ezen kívül egy szövegdobozt, ahova a Delicious felhasználónevet lehet beírni és egy gombot, amit megnyomva megtörténik az adatok lekérdezése és megjelenítése:

  <input type="text" id="txtUserName" />
  <input type="button" value="Utolsó 5 link lekérése" onclick="getLinks();" />

A getLinks metódusban semmi JSONP specifikus nem látszik, csak meghívunk egy webszolgáltatást:

  function getLinks()
  {
    var userName = $get('txtUserName').value;
    var uri = String.format(http://feeds.delicious.com/v2/json/{0}?count=5&plain, 
encodeURI(userName)); Sys.Net.WebServiceProxy.invoke(uri, null, true, null, onComplete); }

Annyi azért lényeges, hogy az invoke függvény harmadik, useGet paramétert true-ra állítjuk, hogy a kérés ne SOAP, hanem HTTP GET legyen.

Sikeres hívás esetén a WebServiceProxy az általunk készített onComplete metódust fogja meghívni, ami mindössze a manuális adatkötést tartalmazza:

  function onComplete(results)
  {
    $find('dvResults').set_data(results);
  }

A háttérben valójában az alábbi HTTP kérés fog kimenni:

  GET /v2/json/balassy?count=5&plain&callback=Sys._jsonp1 HTTP/1.1
Accept: */*
Accept-Language: en-US,hu-HU;q=0.5
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: feeds.delicious.com

Látható, hogy a WebServiceProxy automatikusan létrehoz egy Sys._jsonp1 (automatikusan sorszámozott) callback metódust, ami azután success esetén meg fogja hívni a mi onComplete callback függvényünket.

A teljes példa kipróbálható és letölthető itt.


htm jsonp.htm (2 kB)


Balássy György (MS RD, ASP.NET MVP, MCTS)

Balássy György (MS RD, ASP.NET MVP, MCTS) Villamosmérnök, a BME Automatizálási és Alkalmazott Informatikai Tanszékén webportálok fejlesztését oktatja. 2000 óta foglalkozik a Microsoft .NET platformjával, melynek meghonosításában jelentős szerepet vállalt előadóként, konzulensként és A .NET Framework és programozása című könyv társszerzőjeként. Az MSDN Kompetencia Központon belül a Portál Technológiák Csoport vezetője, szakterülete web alapú rendszerek fejlesztése és üzemeltetése. 2004-ben Magyarországon elsőként kapta meg a Most Valuable Professional címet, majd 2005 óta a Microsoft magyarországi regionális igazgatója. Publikációi a Technet Magazinban, az MSDN Kompetencia Központ honlapján és szakmai blogjában olvashatóak.

2009.12.16. 4:50:20 | Permalink | Hozzászólások: 0 | Tárgyszavak: , , ,


  • LINQ to XML: osztályhierarchia, navigációs és módosító metódusok

    Balássy György (MS RD, ASP.NET MVP, MCTS) Az egyik óriási problémám az XML osztálykönyvtárakkal, hogy nagyon sok időbe kerül, míg sikerül átlátnom, hogy melyik osztály mire való és hogyan kapcsolódik az összes többihez. Az általam ismert XML osztálykönyvtárak közös jellemzője, hogy ugyanazt az eredményt nagyon sokféleképpen lehet elérni, hiszen egy adott elem több úton is megközelíthető. Na de melyik út a legrövidebb? Tovább »
  • Seven – Ready To Go

    Balássy György (MS RD, ASP.NET MVP, MCTS) Ahogy előzetes megígérték – még júliusban – elkészült a Windows 7 és a Windows Server 2008 R2! Nem tudom, hogy ki miért fog átállni, át fog-e egyáltalán, összeszedtem néhány kedvenc és nem annyira kedvenc feature-ömet. Tovább »


Írja meg Ön is véleményét!


Hozzászólásokat csak regisztrált, bejelentkezett felhasználóktól tudunk elfogadni!

Hozzászólások