Saturday, April 16, 2011

JSON-P mit Spring und CXF

Viele Webapplikationen laden Daten per JavaScript im JSON-Format von einem Server in den Browser, um sie dort darzustellen oder zu verarbeiten. Ein solcher Beispieldatensatz im JSON-Format könnte wie folgt aussehen:


{
  “cities”: [”Hamburg”, “New-York”, “Tokio”]
}

Ein Webbrowser lässt allerdings nicht zu, dass ein Script Daten von einer anderen Domain lädt als der, von der die Seite, in die das Script eingebettet wurde, geladen wurde. JavaScript hat also keinen Zugriff auf Daten, die von einer anderen Domain kommen. Diese Einschränkung ist ein Security-Feature des Browsers und als Same Origin Policy bekannt.

Oft ist es aber nötig, Daten von einer fremden Domain oder einer eigenen Subdomain zu ermitteln. Die saubere technische Lösung für dieses Problem ist Cross Origin Resource Sharing. Beim Cross Origin Resource Sharing werden Berechtigungen über HTTP-Header gesteuert. Leider funktioniert Cross Origin Resource Sharing nicht mit Legacy-Browsern, die noch häufig anzutreffen sind.

Ein Workaround, der in jedem Browser funktioniert, ist JSON-P: JSON with Padding. Ein Script darf selbst zwar keine Daten von einer fremden Domain anfordern, aber es darf einen Script-Tag generieren, der ein weiteres JavaScript von dieser fremden Domain lädt und direkt ausführt. Diesem nachgeladenen JavaScript übergibt man als GET-Parameter den Namen einer Callback-Funktion, die von dem nachgeladenen JavaScript ausgeführt werden sollte. Per Konvention trägt dieser GET-Parameter den Namen „_jsonp“.



script src="”http://my.otherdomain.de/rest-ws/myservice/mydata?_jsonp=jsonpCallback“" type="”text/javascript”"



Das so eingebundene Script muss dynamisch auf dem Server in einer Weise erzeugt werden, dass es die übergebene Callback-Funktion aufruft, sobald es vom Browser geladen wurde. Der Callback-Funktion werden in der Regel die Daten übergeben, die man ansonsten direkt als JSON-Objekt angefragt hätte:


jsonpCallback (
 
  {
 
    “cities”: [”Hamburg”, “New-York”, “Tokio”]
 
  }
 
);

Die Callback-Funktion wird von dem Script, das eigentlich die Daten anfordern wollte, bereitgestellt. So werden Daten von einer fremden Domain per JSON-P geladen. Front-End-Libraries wie jQuery stellen eine automatisch generierte Callback-Funktion bereit. Daher muss sich der Entwickler nicht um das Schreiben dieser Callback-Funktion kümmern.


return $.ajax({
 
type: “GET”,
 
url: “http://my.otherdomain.de/rest-ws/myservice/” + data,
 
error: errorcallback,
 
success: callback,
 
dataType: “jsonp”,
 
jsonp: “_jsonp”,
 
jsonpCallback: “ jsonpCallback “
 
});

Auf Client-Seite stellt JSON-P also kein Problem dar und wird von gängigen JavaScript-Bibliotheken unterstützt.

Auf dem Java Enterprise-Server wird für RESTful Services oft eine JAX-RS-Implementierung benutzt. Eine gängige JAX-RS-Implementierung ist Apache CXF. Allerdings stellt CXF im Moment noch kein JSON-P zur Verfügung (JIRA 3005). Daher muss man sich einen eigenen JSON-P-Provider schreiben. Dies ist glücklicherweise sehr einfach, denn man kann alle Funktionalitäten vom Default-JSONProvider erben:


@Produces("application/json")
 
public class JSONPProvider extends JSONProvider {
 
  @Override
 
  public void writeTo(
 
    Object obj, Class cls,
 
    Type genericType, Annotation[] anns,
 
    MediaType m, MultivaluedMap headers,
 
    OutputStream os
 
  ) throws IOException {
 
    final String prefix = request.getParameter("_jsonp"); // fix for demo
 
    final boolean hasPrefix = !StringUtils.isEmpty(prefix);
 
    if(hasPrefix) {
 
      // … check for injection first
 
      os.write(prefix); // simplified
 
      os.write('(');
 
      super.writeTo(obj, cls, genericType, anns, m, headers, os);
 
      os.write(')');
 
    } else {
 
      super.writeTo(obj, cls, genericType, anns, m, headers, os);
 
    }
 
  }
 
}

Dieser eigene JSON-P-Provider macht nichts, außer die vom JSON-Provider erzeugten JSON-Daten mit dem Aufruf einer Callback-Funktion zu umschließen, falls ein Request-Paramter „_jsonp“ übergeben wurde. Das abgedrucke Listing ist simplifiziert. Selbstverständlich muss man den Eingabeparameter „prefix“ prüfen, um eine Code-Injection auszuschließen.

Dieser selbst geschriebene JSON-P-Provider kann im Spring-Framework nun als JSON-P-Provider-Bean definiert werden. Letztere kann in einem JAX-RS-Server genutzt werden.



 
  
 
  
 
  
 
    
 
      
 
    
 
    
 
     
 
   
 
  
 


So lassen sich RESTful Services, die bisher nur JSON sprechen konnten, durch Konfiguration des JSON-P-Providers so erweitern, dass sie auch optional JSON-P sprechen können.

Dies ist ein Cross-Post vom Holisticon-Blog und von Ajaxer.