Jquery cross domain request - felderítés

Jquery cross domain request - felderítés
2012-09-04T15:14:14+02:00
2012-09-05T10:10:22+02:00
2022-11-26T19:05:37+01:00
devel62
Sziasztok!
Nem értek különösebben a javascripthez, meg a böngészők lelki világához sem, úgyhogy előre elnézést kérek, ha valami buta dolgot kérdeznék!

Van egy meglévő rendszerünk, eszközökkel, "felhőben" működő szerverekkel, és az ügyfelekkel.
Az eszközök automatikusan kapcsolódnak a szerverekhez az interneten keresztül, és adatokat cserélnek.
A szervereken van webes felület, ezt az ügyfelek a böngészőjükkel érik el, ezen keresztül használják/menedzselik az eszközöket.

Azt szeretném megoldani, hogy amennyiben az ügyfél egy eszköze az ő számítógépéről közvetlenül elérhető, akkor az adatok ne az eszköz-szerver-böngésző, hanem az eszköz-böngésző útvonalon közlekedjenek (későbbi ajax hívásokra gondolok itt például).

Erre kitaláltam azt, hogy egy cross domain requestet intézek a böngészőből az eszköz ip-címe irányába (amit a szerver tud az oldal legenerálásakor), és ha van válasz, valamint az megfelelő, akkor a későbbiekben a böngésző az eszközt közvetlenül célozza meg a kéréseivel.

A gondom az, hogy ez a dolog nem működik. JQuery-vel, jsonp lekérdezést próbáltam, a következőképpen:

if ($.browser.msie && window.XDomainRequest) { // ez itt csak kísérleti jelleggel megy, IE miatt var xdr = new XDomainRequest(); xdr.timeout=cam_test_timeout; xdr.open('post', 'http://'+cam_ip+'/getver.cgi'); xdr.onerror = function(event) { useDirectCam(); }; xdr.ontimeout = function () { useProxyServer(); }; xdr.onload = function() { useDirectCam(); }; xdr.withCredentials = true; xdr.send(); } else { // Ez a lényeg... $.ajax({ type: 'POST', url: 'http://'+cam_ip+'/getver.cgi?callback=?', dataType: 'jsonp', crossDomain: 'true', timeout: cam_test_timeout, data: '', success:function(data){ console.log('success'); useDirectCam(); }, error:function (xhr, ajaxOptions, thrownError){ console.log('error: ' + xhr.status); console.log('error: ' + thrownError); if (thrownError=='') useDirectCam(); else useProxyServer(); }, complete: function (xhr, textStatus) { console.log('complete'); } }); }

Eredetileg nem jsonp-vel próbálkoztam, de amennyire ki tudtam bogarászni, ezt kell használnom, különben nem fog menni a cross domain lekérdezés.
Az eszközben ez a "getver.cgi" egy korlátozott képességű lighttpd alatt futó, php-szerű nyelven íródott szkript és a következőt csinálja:

<? header("Content-type: application/json"); $fp = @fopen("/usr/lib/version", "r"); if ($fp != -1) { $x=fgets($fp, 255); @fclose($fp); echo "{asd:$x}"; } >

Mindkét kódrészlet csak kísérleti jellegű.

Az elvárt működés tehát az lenne, hogy:
- ha az adott címen fut webszerver, és ebben a getver.cgi olyan választ ad vissza, amit szeretünk, akkor a SUCCESS eseménykezelő fusson le
- minden más esetben az ERROR
- egyéb kritérium, hogy akkor is az ERROR fusson le, ha az adott ip-címen van webszerver, de az nem a mi eszközünk (hanem egy másik, ismeretlen webszerver), és pl. nincs rajta getver.cgi (tehát 404-es hiba)
- IE 8+, FF, Chrome és Safari böngészőben kellene futnia

Az aktuális működés:
- mindig az ERROR hívódik meg, a hibakód 200, a thrownError pedig azt mondja, hogy "Error: jQuery17209219800518497208_1346763313174 was not called" (más számokkal persze)
- ha nem JSONP-t vagy JSON-t adok meg, akkor a hívás sikertelen (firebug pirossal írja ki), hibakód 0, throwError üres string
- nem számít, hogy a getver.cgi-ben bemondom-e a headert vagy hogy milyen adatokat mondok vissza, a viselkedés nem változik

Mit csinálok rosszul? Illetve, van erre jobb megoldás is?

Mivel nekem azt kell tudnom eldönteni, hogy a böngésző felől elérhető-e az eszköz, ezért nem megoldás a szerver oldali php proxy. Érzékeny adatot nem kell átvinni, egyszerűen csak annyit szeretnék kideríteni, hogy egy ismert ip-címen az az eszköz van-e, akivel mi társalogni akarunk majd a későbbiekben, vagy pedig muszáj lesz a fallback a szerveren keresztül történő adatgyűjtésre.

Van egy olyan érzésem, hogy a megoldás az orrom előtt van...

Előre is köszönöm a segítséget!

szerk:
Elfelejtettem írni, hogy 1.7.2-es jquery-t használok.
Mutasd a teljes hozzászólást!
Egy dolgot előre kell bocsátani: a szerver oldaladat mindenképp változtatni kell. A cross-origin megoldások direkt úgy vannak kitalálva, hogy az őket nem ismerő szervereken ne működjenek, vagyis ha az adott szerver eddig nem számított más domainről jövő kérésekre, akkor ezzel ne nyissanak új támadási felületeket felé.

Két irányba lehet szerintem itt próbálkozni:
1. Mezei cross-origin AJAX kérés, más néven CORS. Ez már majdnem minden böngészőben támogatott, csak IE-ben kicsit más interface-e van, mint a többieknél. Sima GET kérés esetén elég egy extra header ahhoz, hogy a szervered válaszát a kliens oldali szkripted láthassa:
Access-Control-Allow-Origin: *
(Ebben a formában a világon minden szkript felé láthatóvá teszed, de ha nincs benne érzékeny adat, akkor ez is simán megfelel.) Ha ezt választod, akkor NE állíts be JSONP-t a jQuery-nek, és metódus lehet mezei GET vagy POST.
2. JSONP kérés. Ez tutira megy minden böngészőben, mivel ez a régebbi megoldás. Hátránya, hogy kizárólag GET kérést lehet vele küldeni, és a válasz csak (enyhén módosított) JSON formátumban jöhet vissza a szervertől. A hibadetektálás is korlátozott, a JS kód nem kap direkt visszajelzést arról, hogyha nem sikerül a kérés. Valami ilyesmi kell a szerver oldalra:
<? header("Content-type: application/javascript"); echo $_GET['callback'].'('; $fp = @fopen("/usr/lib/version", "r"); if ($fp != -1) { $x=fgets($fp, 255); @fclose($fp); echo "{asd:$x}"; } echo ')'; >
Ez a megoldás lényegében egy nagy hack: a kliens egy query paraméterben ad neked egy függvénynevet. Te a válaszodat úgy írod meg, hogy egy függvényhívás legyen erre a függvényre. A kliens úgy fog az egészhez hozzáférni, hogy gyárt egy új <script> taget a te URL-eddel, így a böngésző lefuttatja a szervered által visszaadott kódot. (Persze biztonsági kockázat is van, mert ha a távoli címen gonosz szerver van, akkor tetszőleges kódot tud futtatni a kliensen.)

A lényeg, hogy a kettőt ne keverd, mint ahogy most próbálod. Ha lehet, inkább az első opciót preferáld, mert az jóval kényelmesebb, mint a második. JSONP-t csak akkor javaslok, ha régi böngészőkkel is mennie kell a cuccnak.
Mutasd a teljes hozzászólást!

  • ez mit ad vissza?
    http://az_ipd/getver.cgi
    Mutasd a teljes hozzászólást!
  • ja és szerver oldalon valami ilyesmi kell a jsonp -hez

    header("Content-type: application/json"); $fp = @fopen("/usr/lib/version", "r"); if ($fp != -1) { $x=fgets($fp, 255); @fclose($fp); echo $_GET["callback"]."({asd:$x})"; }
    Mutasd a teljes hozzászólást!
  • Egy dolgot előre kell bocsátani: a szerver oldaladat mindenképp változtatni kell. A cross-origin megoldások direkt úgy vannak kitalálva, hogy az őket nem ismerő szervereken ne működjenek, vagyis ha az adott szerver eddig nem számított más domainről jövő kérésekre, akkor ezzel ne nyissanak új támadási felületeket felé.

    Két irányba lehet szerintem itt próbálkozni:
    1. Mezei cross-origin AJAX kérés, más néven CORS. Ez már majdnem minden böngészőben támogatott, csak IE-ben kicsit más interface-e van, mint a többieknél. Sima GET kérés esetén elég egy extra header ahhoz, hogy a szervered válaszát a kliens oldali szkripted láthassa:
    Access-Control-Allow-Origin: *
    (Ebben a formában a világon minden szkript felé láthatóvá teszed, de ha nincs benne érzékeny adat, akkor ez is simán megfelel.) Ha ezt választod, akkor NE állíts be JSONP-t a jQuery-nek, és metódus lehet mezei GET vagy POST.
    2. JSONP kérés. Ez tutira megy minden böngészőben, mivel ez a régebbi megoldás. Hátránya, hogy kizárólag GET kérést lehet vele küldeni, és a válasz csak (enyhén módosított) JSON formátumban jöhet vissza a szervertől. A hibadetektálás is korlátozott, a JS kód nem kap direkt visszajelzést arról, hogyha nem sikerül a kérés. Valami ilyesmi kell a szerver oldalra:
    <? header("Content-type: application/javascript"); echo $_GET['callback'].'('; $fp = @fopen("/usr/lib/version", "r"); if ($fp != -1) { $x=fgets($fp, 255); @fclose($fp); echo "{asd:$x}"; } echo ')'; >
    Ez a megoldás lényegében egy nagy hack: a kliens egy query paraméterben ad neked egy függvénynevet. Te a válaszodat úgy írod meg, hogy egy függvényhívás legyen erre a függvényre. A kliens úgy fog az egészhez hozzáférni, hogy gyárt egy új <script> taget a te URL-eddel, így a böngésző lefuttatja a szervered által visszaadott kódot. (Persze biztonsági kockázat is van, mert ha a távoli címen gonosz szerver van, akkor tetszőleges kódot tud futtatni a kliensen.)

    A lényeg, hogy a kettőt ne keverd, mint ahogy most próbálod. Ha lehet, inkább az első opciót preferáld, mert az jóval kényelmesebb, mint a második. JSONP-t csak akkor javaslok, ha régi böngészőkkel is mennie kell a cuccnak.
    Mutasd a teljes hozzászólást!
  • {asd:versioninfo}
    Mutasd a teljes hozzászólást!
  • tehát eléred a szervert.
    Ahogy Csaboka -is írta ha maradsz a jsonp -nél ez lesz a megoldás.

    echo $_GET["callback"]."({asd:$x})";
    Mutasd a teljes hozzászólást!
  • Bakker, sejtettem, hogy csak egy lépés hiányzott.
    A következőképpen módosítottam az eszközön a GETVER.CGI-t:

    <? header("Access-Control-Allow-Origin: *"); $fp = @fopen("/usr/lib/version", "r"); if ($fp != -1) { $x=fgets($fp, 255); @fclose($fp); echo $x; } >

    Az ajax kérést a böngésző oldalán pedig így:

    if ($.browser.msie && window.XDomainRequest) { var xdr = new XDomainRequest(); xdr.timeout=cam_test_timeout; xdr.open('get', 'http://'+cam_ip+'/getver.cgi'); xdr.onerror = function(event) { useProxyServer(); }; xdr.ontimeout = function () { useProxyServer(); }; xdr.onload = function() { useDirectCam(); }; xdr.withCredentials = true; xdr.send(); } else { $.ajax({ type: 'GET', url: 'http://'+cam_ip+'/getver.cgi', dataType: 'text', crossDomain: 'true', timeout: cam_test_timeout, data: '', success:function(data){ useDirectCam(); }, error:function (xhr, ajaxOptions, thrownError){ useProxyServer(); } }); }

    Az eredmény:
    - ha eléri a CGI szkriptet, akkor visszakapom a version fájl tartalmát
    - ha a webszerveren nincs ott a keresett fájl, akkor ERROR-ra fut (0 hibakód és üres a thrownError,, IE-nél onerror)
    - ha az adott címen nincs is webszerver vagy semmilyen eszköz, akkor "timeout" hibával jön vissza a megadott idő után, IE-nél ontimeout

    Szóval teljesen jó. Köszönöm a segítséget!
    Mutasd a teljes hozzászólást!
  • Neked is köszönöm a javaslatokat!
    Első körben próbáltam így megoldani, de továbbra sem működött.
    Ha a cgi szkriptet meghívtam böngészőből így:

    http://ip/getver.cgi?callback=123

    akkor azt adta vissza, hogy

    123({asd:versioninfo})

    De valamiért ugyanaz maradt a hibaüzenet a böngészőben (folyton az "error" eseménykezelő hívódik meg és "... was not called" a hibaüzenet). Nem tudom mi lehet vele probléma...
    Mutasd a teljes hozzászólást!
  • Nem tudom mi lehet vele probléma...


    Valószínű az, hogy nem szabályos JSON, mert a sztring literál nincs idézőjelbe rakva. Így lenne szabályos:
    123({"asd":"versioninfo"})
    (mondjuk így se, mert az 123 nem helyes függvénynév, de érted mire gondolok)

    De ezt inkább csak az utánad jövőknek írom, tájékoztatásképpen, mert a mezei GET-es megoldás a jobb, nem érdemes lecserélni a javított JSONP-re.
    Mutasd a teljes hozzászólást!
  • Értem, és köszi!
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd