Talán segíthetek egy-két hozzám hasonló AJAX-os kezdőnek, ha megosztom veletek a ma délelőtti tapasztalataimat. A dologhoz tudni kell, hogy egy kliens-szerver alapú webalkalmazást írok. A kommunikáció természetesen AJAX-on keresztül történik. Ma délelőtt a helyi gépemen való tesztelés után feltöltöttem a kódot a tárhelyemre, hogy fél-éles körülmények között is tesztelhessem azt. URL-esztétikai szempontok miatt (:]) a szervert a http://server.domain.hu, a klienst pedig a http://client.domain.hu címre raktam. A tárhelyszolgáltató adminisztrációs felületén beállítottam az aldomaineket, feltöltöttem a fájlokat, és a böngészőmet a kliens oldalára irányítottam. Semmi nem történt. A JavaScript-konzol viszont üres volt, hiba tehát – úgy tűnt – nem keletkezett.

Néhány alapvető teszt után kiderült, hogy a kliens nemes egyszerűséggel nem kapcsolódott a szerverhez. Kezdődhetett a hibakeresés! Jobb híján mentem a "programozó-orrom" után – és mint általában, ez most is bevált.

Az XMLHttp-hez az alkalmazás a Prototype-ot használja:

new Ajax.Request(this.Paths['server'] + '?' + _serialize(parameters), opt);

Bár nem szeretek más kódjában turkálni, most úgy tűnt nincs más megoldás. Tovább a prototype.js sötét bugyraiba. (Bár lehet, hogy inkább csak én vagyok a sötét hozzájuk :]). A lényeget kerestem, azt a pontot, ahol létrejön a kapcsolat. Gyorsan megtaláltam:

try {

    /* ... */

    // a this.transport maga az XMLHttpRequest objektum
    this.transport.open(this.options.method, this.url, this.options.asynchronous);

    /* ... */

} catch (e) {

      this.dispatchException(e);

}

Betettem az open() elé egy teszt-célú alert('***'); parancsot. Lefutott. Aztán beraktam ugyanezt az open() mögé. Láss csodát, a három csillag nem jelent meg a képernyőn. Hiba viszont még mindig nem keletkezett a JS-konzolon, de legalább már láttam, hogy ennek az oka valószínűleg a try/catch blokk lehet. Közelebbről megnézve a kivételeket kezelő dispatchException() metódust kiderült, hogy ha az Ajax.Request-nek nincs megadva egy onException paraméter, akkor kivétel esetén a vezérlés a Prototype.emptyFunction függvényre kerül. Ez a függvény pedig a nevéhez hűen nem csinál semmit. Ergo: a kivételek mennek a /dev/null-ba. Ha-ha-ha, nagyon vicces. Kivettem a try/catch blokkot. Újratöltés után szinte örömmel üdvözöltem a JS-hibát jelző kis piros ikszet:

Error: uncaught exception: Permission denied to call method XMLHttpRequest.open

És itt jön a tanulság. Egy Google-keresés fényt derített a probléma gyökerére: ez a kivétel nem programozási hiba, hanem egy Cross-Site Scripting (XSS) elleni biztonsági intézkedés hatására keletkezett. Flash-es kollégáknak gondolom már ismerős lesz az elv: az XMLHttpRequest objektummal csak arról a webszerverről lehet adatot letölteni, ahonnan az éppen futó szkript is származik. (Én pedig a klienst és a szervert ugye két külön aldomain alá raktam.)

A "hiba" mind Opera, mind Firefox alatt jelentkezett. (IE alatt más problémák is voltak.) De hogyan tovább? A legegyszerűbb út a közös domain használata; azonban a Mozilla böngészői más megoldást is nyújtanak a problémára: A cég signtool nevű eszközével a webfejlesztőknek lehetőségük nyílik arra, hogy szkriptjeiket digitálisan aláírva terjesszék. Az ilyen kód extra jogosultságokat szerezhet, persze csak a felhasználó beleegyezésével. Gyakorlatban a jogosultság-kérés így néz ki:

netscape.security.PrivilegeManager.enablePrivilege("UniversalPreferencesRead");

A böngésző ezen a ponton megkérdezi a felhasználót, hogy szeretné-e engedélyezni az adott szkriptnek a nem-biztonságos lehetőségekhez való hozzáférést. Ha a felhasználó az "Engedélyez" gombra kattint, szabad az út!

Ennek a Mozilla-funkciónak persze más felhasználási területei is vannak. Még jónéhány olyan dolog létezik, amelynek a használatához engedélyt kell kérnünk a felhasználótól. Nézzük, milyen perverzióhoz milyen jogosultság szükséges:

Jogosultság Funkciók
UniversalBrowserRead
  • about: kezdetű URL-ek betöltése.
  • A history objektum használata. Ezzel adatok szerezhetőek a felhasználó által meglátogatott oldalakról.
  • about: kezdetű URL-ek betöltése.
UniversalBrowserWrite
  • A menü, a görgetősáv, az állapotsor vagy az eszköztár hozzáadása/törlése a window objektummal.
  • Az ablak bezárása, 100×100 pixeles méretnél kisebbre méretezése, vagy a képernyő területén kívülre mozgatása.
UniversalPreferencesRead és UniversalPreferencesWrite
  • A böngésző tulajdonságainak lekérdezése/beállítása a preference metódussal.
UniversalXPConnect
  • A böngésző API-jának korlátozás nélküli elérése az XPConnect-en keresztül.
UniversalFileRead
  • A file:// kezdetű URL-ek betöltése.
  • Fájlok automatikus feltöltése az <input type="file" /> használatával.

Úgy látom, kissé elkanyarodtam az eredeti témánktól. Sebaj, legalább volt alkalmam megismertetni titeket a webfejlesztés egy érdekes részletével. Remélem túléltétek :].

Konklúzió:

  1. Az XMLHttpRequest-tel csak a szkriptet tartalmazó szerverről lehet adatokat letölteni, az XSS-t xmegelőzendő.
  2. Olvasdd el a rendelkezésedre álló dokumentációt, mielőtt dolgozni kezdesz más kódjával.
  3. Ha a JavaScript alkalmazásod nem teszi a dolgát, de hibát nem észlelsz, az elsők közt gondolj arra, hogy a try/catch blokkok "elnyelhetik" a hibaüzenetet, ami így nem kerül ki a JavaScript-konzolra.

További szakirodalom (és források):