Fuori programma
01-03-2010 17.14.20 GMT+02La battaglia di aNobii (parte 3)La saga dello sviluppo dell'aNobii Filtered Blog Badge prosegue, eccomi che fronteggio CORS...

Con questo post giungo quasi all'epilogo della vicenda che mi ha portato a sviluppare l'aNobii Filtered Blog Badge. Per chi si fosse perso il secondo episodio lo trova qui.

Ero arrivato a dover bypassare il problema delle protezioni cross-site e c'erano fondamentalmente due vie:

  • contare sul supporto delle specifiche CORS;
  • ricorrere ad una libreria esterna che implementasse chiamate cross-site.

Nel primo caso, dove CORS sta per Cross-Origin Resource Sharing, stiamo parlando di una tecnologia piuttosto recente, tanto che mentre sto scrivendo le specifiche W3C sono ancora a livello di bozza.
In buona sostanza, ci si è resi conto che le protezioni implementate dai browser, pur risolvendo i problemi di sicurezza, creavano troppe limitazioni ai mash-up che sono la forza del Web2.0. Per superarle è stata creata questa estensione che consiste in un'implementazione modificata dell'oggetto XMLHttpRequest, disponibile nei browser a partire da FireFox 3.5, Safari 4, di Opera non si sa nulla (la cosa divertente è che il documento di specifica è stato fatto da Anne van Kesteren di Opera Software), mentre per IE occorre la versione 8 e comunque ci sono delle differenze che vedremo dopo; inoltre, sono necessari degli accorgimenti nell'implementazione della parte server-side che deve rispondere. Per l'oggetto XMLHttpRequest cambia poco o nulla in termini di codice lato client, le novità stanno proprio nei nuovi HTTP header di risposta che vanno implementati sul server.

C'è un piccolo excursus da fare a proposito delle richieste che appaiono nella specifica sotto il nome di preflight request; si tratta di chiamate ormai piuttosto tipiche per AJAX che consistono in una pre-richiesta fatta dal client (implicitamente dall'oggetto XMLHttpRequest, per cui non dovete fare nulla) che serve a verificare se la richiesta sarà autorizzata dal server. Contengono informazioni sull'eventuale autenticazione (che non tratterò), sul tipo di richieste soddisfacibili (GET, POST etc...) e così via...Se la richiesta di preflight viene confermata, il client procede con la richiesta vera e propria. Gli header che in questa prima fase ho inserito nella risposta alla chiamata sono:

Response.ClearContent();
Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
            
// following headers are required to prevent crossite request 
// inhibition (CORS specifications and more)
Response.AddHeader("Access-Control-Allow-Origin", "*");
Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
Response.AddHeader("Access-Control-Max-Age", "600");
    

Dove Access-Control-Allow-Origin limita le richieste provenienti da specifiche origini (volevo consentire che la weblet venisse usata da chiunque perciò ho specificato * che sta per "qualunque origine" e non ho ovviamente operato un controllo preventivo sull'origine della richiesta). Access-Control-Allow-Methods stabilisce i metodi di richiesta ammessi; il valore OPTIONS prevede appunto le richieste di preflight che sono fatte con questo metodo.
Access-Control-Max-Age è interessante perchè consente di beneficiare della preflight result cache; nel caso il client la implementi, le richieste di preflight vengono conservate in una cache fino alla scadenza, dettata dal valore (in secondi) di Access-Control-Max-Age, così da non essere ripetute inutilmente. C'è da rimarcare che il traffico con il server, nel caso di chiamate AJAX, può essere significativo ed una richiesta ridondante in meno è di certo gradita.
Segnalo per chi volesse approfondire, questo interessante articolo sull'argomento CORS che è un po' più discorsivo della fredda specifica W3C.

Non restava che chiudere la chiamata codificando la risposta in formato JSON (io ho usato la libreria di Jayrock per .NET); con Safari ho sperimentato funziona anche senza codifica.

Response.Write(Jayrock.Json.Conversion.JsonConvert.ExportToString(output));
Response.End();

Rimaneva la parte del client e veniamo a IE8 su cui avevo sorvolato prima.
Nonostante quelli di Microsoft siano gli inventori di XMLHttpRequest che era stato creato per consentire l'implementazione del client web di Outlook, mentre gli altri produttori di browser modificavano l'oggetto in questione per adempiere a CORS, in Microsoft - sempre per essere originali - hanno pensato bene di creare un nuovo oggetto: XDomainRequest.
Il codice client standard per chiamate AJAX doveva perciò essere modificato per gestire sia la creazione del del'oggetto XMLHttpRequest, ma anche di XDomainRequest (io ho anche gestito gli altri classici ActiveX, ma non funzionano - se non in locale o sullo stesso server del chiamante - per i motivi di sicurezza citati).
Per fortuna, l'oggetto non è molto diverso sia per la gestione che per la chiamata di callback come si può vedere.
Per la decodifica JSON ho usato la libreria ufficiale

Funzionava tutto, purtroppo quello che non mi andava giù è che dovessi puntare solo su client molto recenti, che pur se diffusi si e no coprono il 40% dell'installato.
Vi lascio perciò il codice JavaScript, ma dovrete attendere la prossima puntata per vedere quella che poi è divenuta l'implementazione finale che gira invece su tutti i browser.

// remember to include in your code the link to Json JavaScript decode library
// <script language="Javascript" type='text/javascript' src='json.js'></script>
    
    // calls the url and injects into target HTML element the result
    function ajaxGET(url, query, target) 
    {
        // XmlHttpRequest solution
        var http_request = init(target);
    
        var getUrl = url + '?' + query;
        
        if (window.XDomainRequest)
        {
            http_request.open("get", getUrl);
            http_request.send();
        }
        else
        {
            http_request.open('GET', getUrl, true);
            http_request.send(null);
        }   
    }    
    
    // response handler for XmlHttpRequest and XDomainRequest
    function processResponse(http_request, target) 
    {
        if (window.XDomainRequest)
        {
                document.getElementById(target).innerHTML = 
JSON.parse(http_request.responseText); return; } if (http_request.readyState == 4) { if (http_request.status >= 200 &&
http_request.status < 300 ) { document.getElementById(target).innerHTML =
JSON.parse(http_request.responseText); } else { document.getElementById(target).innerHTML =
'Sorry, not available (status ' +
http_request.status + ' - ' +
http_request.statusText + ').'; } } } // initializes the object (depending on browser) that could
// execute XMLHttpRequest or
// XDomainRequest and binds it to the target area function init(target) { document.getElementById(target).innerHTML = '<span class="anobiiLoading">loading...<br/><br/>if after a while<br/>' + ' you don\'t obtain the result,<br/> click ' + '<span onclick="alert(\'Check that your browser is CORS - ' + 'Cross-Origin Resource Sharing - enabled.\');" ' + '><u>here</u></span></span>'; var http_request = false; if (window.XDomainRequest) { // IE8 http_request = new XDomainRequest(); } else if (window.XMLHttpRequest) { // FireFox, Chrome, Safari, etc... http_request = new XMLHttpRequest(); if (http_request.overrideMimeType) { http_request.overrideMimeType('text/xml'); } } else if (window.ActiveXObject) { // IE7 or less try { http_request = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { http_request = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { // NOP } } } if (!http_request) { document.getElementById(target).innerHTML = 'Sorry, this weblet works just with XMLHTTPRequest ' + 'or XDomainRequest enabled browsers.'; return null; } if (window.XDomainRequest) http_request.onload = function () { processResponse(http_request, target); }; // IE8 else http_request.onreadystatechange = function () { processResponse(http_request, target); }; // others return http_request; }
data modifica 01-03-2010 20.07.21 GMT+02

#

Non ci sono commentiFeed RSS 2.0 di : Fuori programma, commenti a "La battaglia di aNobii (parte 3)"
Aggiungi un commento
Mittente : *
Email : 
Web : 
Testo del messaggio : * 
Codice di validazione : * 

otherbit, by Cosimo Carbonelli m. info@otherbit.com p.i. 11743080159
made with OtherWeb