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.

Az 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.

 

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s