Samstag, Mai 11, 2013

jQuery, Ajax und Cross Origin

Die Ajax Funktion von jQuery ist bekanntlich sehr praktisch. Letztens bin ich mit dieser Funktion auf einige Probleme gestossen. Hier das Grundgerüst für die Problemstellung: Ein Javascript Ajax Aufruf der eine JSON Response vom Server erwartet. Laut jQuery Dokumentation kann die Ajax Funktion eigenständig erkennen, dass die Response im JSON Format vorliegt und konvertiert die Response in ein entsprechendes Javascript Objekt. Ein eigener Aufruf von $.parseJSON(...) kann also bei der Verarbeitung der Transport Nachricht entfallen. Im IE 9 und unter Chrome hatte ich mit diesem Verhalten keine Probleme. Unter Firefox hat dieser Automatismus aber nicht funktioniert. In der Dokumentation zu jQuery findet sich der folgende Hinweis:
At present, due to a bug in Firefox where .getAllResponseHeaders() returns the empty string although .getResponseHeader('Content-Type') returns a non-empty string, automatically decoding JSON CORS responses in Firefox with jQuery is not supported.
Zu finden unter jQuery.ajax()
Das Problem hatte ich mir natürlich selbst eingebrockt. Die Javascript Resource, die den Ajax Aufruf absetzen möchte, residiert unter der URL
http://tippdiekistebier.localhost/aktuell/auto_1tipp.html
Der Ajax Aufruf ist ein POST an die URL
http://tippdiekistebier.localhost:8080/betoffice-jweb/tipp/submit
Damit handelt es sich um ein sogenanntes "Cross-Origin Resource Sharing". Die Server URL ist die vermeintlich Gleiche. Ausschlaggebend ist hier aber die zusätzliche Port Nummer für den POST Befehl. Damit der Browser die Response verarbeitet, muss der Server im Response Header die Eigenschaft
response.setHeader("Access-Control-Allow-Origin", "*");
setzen. Falls diese Eigenschaft nicht gesetzt ist, kann der Browser die Response verweigern, was die mir bekannten Browser auch alle so umsetzen. Zusätzlich dazu setze ich den Content-Type.
response.setHeader("Content-Type", "application/json; charset=UTF-8");
Die jQuery Ajax Funktion kann die Response dem JSON Format zuordnen und automatisch ein Javascript Objekt erzeugen. Das funktioniert mit einer aktuellen Chrome Version, wie auch mit dem Internet Explorer 9. Unter Firefox findet die automatische Konvertierung nicht statt. Einen Hinweis zu diesem Problem findet sich in der Doku von jQuery (siehe oben). Ich muss also unterscheiden, ob ich die JSON Konvertierung selbst durchführe oder diese bereits von jQuery durchgeführt wurde. Im Beispielcode frage ich das Transport Objekt nach einer erwarteten Eigenschaft ab. Ist diese nicht definiert, führe ich die Funktion $.parseJSON(...) aus. Danach kann die eigentliche Verarbeitung der Response erfolgen.
$.ajax({
    type : 'POST',
    url : 'http://tippdiekistebier.localhost:8080/betoffice-jweb/tipp/submit',
    data : my_data,
    cache : false,
    success : function(transport) {
        if (transport === undefined) {
            _messageSender.successButNoResponse();
        } else {
            try {
                var jsonResponse = transport;
                if (transport.notOk === undefined
                        || transport.ok === undefined) {
                    jsonResponse = $.parseJSON(transport);
                }

                if (jsonResponse.notOk) {
                    ....
                } else if (jsonResponse.ok) {
                    ....
                }
                
            } catch (exception) {
                _messageSender.unexpectedError(exception);
            }
        },
    error :  function(xhr, ajaxOptions, thrownError) {
        _messageSender.unexpectedError(thrownError);

        if (console && console.log) {
            console.log("Ajax Request Status: " + xhr.status);
            console.dir(thrownError);
        }
});
Kennt man das Problem, ist die Lösung relativ naheliegend. Mich hat das jedoch einige Stunden Javascript Debugging gekostet.
UPDATE: 29.11.2014
Weiter Informationen zu dem Thema CORS (Cross-Origin Resource Sharing) finden sich zum Beispiel:

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...