Mittwoch, 20. April 2011

Ajax oder doch lieber Api-Call

Dank JavaScript ist es möglich selbst auch statische Seiten dynamisch zu gestalten. Dabei bietet Ajax die einfache Möglichkeit Elemente nach zu laden. So kann man die Angangsseiten schlank halten und die benötigten Elemente bei Bedarf nach laden. Ajax hat allerdings ein Sicherheitsschutz, der es schwer macht Cluster oder gar verteilte Seiten zu verbinden. Hier ist das Thema Crosssdomain von Bedeutung.

Crossdomain Problem
Aus Sicherheitsgründen ist es einer Webseite nicht erlaubt per Ajax auf andere Subdomains zugreifen zu können. Wenn jemand eine komplette Domain für sich selbst nutzt scheint es erstmals unnötig, doch wenn man Subdomains nutzt und andere ebenso Subdomains mit der gleichen Domain nutzen, wird diese Sicherheitshürde verständlicher. Eigentlich ist das kein grosses Problem, wenn man nur eigene Inhalte vom eigenen Server herunterladen möchte. Doch was ist, wenn man verschiedene Subdomains einsetzen möchte, um zum Beispiele verschiedene Kunden zu unterscheiden und dennoch allen eine gemeinsame Komponente anbieten möchte? Oder man hat soviel Traffic, dass man verschiedene Server nutzt und diese eventuell für Zugriffe aus verschiedenen Kontinenten verteilt hat. Klar man kann nun wieder ein komplexes System mit Loadbalancer und dergleichen aufbauen, doch unser Ziel ist es ja effizient und einfach zum Ziel zu kommen. Vorallem sollten so wenig externe Komponenten wie möglich zum Einsatz kommen. Interessant ist auch die Möglichkeit SSL für das nachträgliche Nachladen zu nutzen, und so die Zugriffspfade für versteckte Dateien zu verstecken. Ein wildcard-SSL-Zertifikat ist schnell erstellt, doch wenige besitzen einen CA, der in allen Browser implementiert ist. Der lästige Browserhinweis ist kaum einem Kunden zu zumuten. Mehrere Zertifikate zu kaufen, ist auch nicht billig. Also könnte man ein Zertifikat erwerben und darüber die SSL Kommunikation machen. Sagen wir mal für die subdomain www. Nun ist es ärgerlich wenn einer eine andere subdomain eingibt, oder eventuell überhaupt keine Subdomain in die Adressleiste des Browser eintippt. Sollten wir diesen Kunden ignorieren? Nein, klar nicht. Ein Umleitung auf die richtige Subdomain ist möglich, doch was ist, wenn Parameter übergeben werden. Diese müsste man auch immer mit umbiegen. Nicht sehr effizient, aber machbar. Wir könnten aber auch als das so sein lassen, wie es ist und statt einer Ajax-Lösung eine andere Methode nutzen, um dynamisch Inhalte nachzuladen. Und das auch noch ohne an  Sicherheitsschranken zu stossen.

Api-Call
Leider habe ich kein richtiges Wort für diese Methode und wir nannten sie immer intern die Apicall-Methode. Darum will ich auch bei diesem Namen bleiben. Sie ist simple wie genial, erfordert jedoch auch eine gewissen Umstellung der Daten. Die Idee ist, dass per JavaScript dynamisch JavaScript Dateien nachgeladen werden. So wird bei Bedarf einfach ein Skript-Tag eingebunden. Dieses JavaScript Code Stück enthält dann die Daten. Diese müssen nur in einer speziellen Form verfasst sein. Man könnte direkt eine JSON ablegen und danach pollen. Doch einfacher geht es, wenn man einen Funktionsaufruf erstellt und dieser die Daten als Parameter enthält. Man kann da beliebig komplex werden. Allerdings hat diese Methode ein paar gravierende Nachteil gegenüber Ajax. Es fehlen die Informationen zum Netzwerkstatus. Man kann also nicht mit absoluter Sicherheit sagen, ob eine Ressource fehlt. Doch da kann man sich grob behelfen. Mit dieser Methode kann man nun auch von anderen Diensten nur mittels JavaScript Daten einlesen, sofern diese den apicall unterstützen. Das ganze ohne einen Serverdienst, der als Proxy fungiert.

Was brauchen wir dafür?
Nicht viel. Wir sollten zuvor 2 Funktionen erstellen, die das Handling machen und können dann loslegen  Daten einzulesen. Die Daten können von dynamischen oder statische Server stammen. Die Daten müssen allerdings aufbereitet werden. Aber man gewöhnt sich schnell dran und vorallem wenn dynamische Programme diese Dateien erstellen, kann man dies schnell erledigen lassen.
Damit wir etwas Error-Handling betreiben habe ich noch eine dritte Funktion hinzugefügt. Die man aber auch weglassen kann. Damit die Kommunikation zwischen den Funktionen einfach gehalten werden kann und wir nicht extra eine Klasse definieren müssen, legen wir auch eine globale Variable an. Also eine Variable direkt im Skriptbereich. Ein komplexerer Variante, aber immer noch reine Funktionen findet man in der http://directpaylink.com/javascript/basics.js

Die JavaScipt Funktionen
var system = new Object();

function apicall(url, onSuccess, onError, onErrorTimeout, urlid) {
  if (!system)
    system = {};
  if (!system['dscript'])
    system['dscript'] = {'id':1, 'cb':{}, 'urls':{}};  
  var id = system["dscript"]["id"];
  system["dscript"]["cb"][id] = {"url": url, 
"onSuccess": onSuccess, "onError": onError, 
"time": new Date().getTime()};
  var refurl = url;
  if (!urlid)
    urlid = refurl
  system["dscript"]["urls"][urlid] = id; // für die statischen URLs
  var head    = document.getElementsByTagName("head")[0];
  var script  = document.createElement('script');
  script.id   = 'obj_dscript_'+id;
  script.type = 'text/javascript';    
var srcurl = url;
  if (srcurl.indexOf("?")<1)
    srcurl += "?"      
  script.src  = srcurl+"&cbf=RS_APIDispatcher&cbid="+id;
  var d = new Date();
  script.src += '&t='+d.getTime();
  head.appendChild(script);
  var timeout = 5000; // 5 sekunden
  if (onErrorTimeout)
    timeout = onErrorTimeout; 
  var errorcheck = setTimeout("RS_APIErroLoadCheck('"+id+"')", timeout);
  system["dscript"]["cb"][id]['errorcheck'] = errorcheck;    
  system["dscript"]["id"]++;
}   
function RS_APIDispatcher(id, result) {
  if (id == 0 && system["dscript"]["urls"][id])
    id = system["dscript"]["urls"][id];
  if (!document.getElementById('obj_dscript_'+id))
    return false;
  if (system["dscript"]["cb"][id])
    system["dscript"]["cb"][id]['dontkill'] = true;
  if (system["dscript"]["cb"][id] && system["dscript"]["cb"][id].errorcheck)  
    clearTimeout(system["dscript"]["cb"][id].errorcheck);
  var old = document.getElementById('obj_dscript_'+id);
  if (old != null) {
    old.parentNode.removeChild(old);
    delete old;
  }
  var oobj = null;
  if(typeof result == 'function')
    result = result();
  else if(typeof result == 'object') 
    oobj = result; 
  var obj = {"responseText": result, "object": oobj};
// errorload could have killed it already
if (system["dscript"]["cb"][id]) system["dscript"]["cb"][id].onSuccess(obj); 
delete(system["dscript"]["cb"][id]);   
}
function RS_APIErroLoadCheck(apicallid){                            
  if (!system["dscript"]["cb"][apicallid])
    return false;
  var objekt = system["dscript"]["cb"][apicallid];
if (system["dscript"]["cb"][apicallid]['dontkill'])
    return false;
  // immernoch da, also wahrscheinlich fehler aufgetreten 
  if (objekt.onError)
    objekt.onError(apicallid); 
  delete(system["dscript"]["cb"][apicallid]);
}

Erklärungen zu dem Code
Die erste Funktion ist der eigentliche Aufruf. Diese prüft ob die globale Variable existiert und deklariert gegebenenfalls eine. Genauso werden dann spezielle Variablen angelegt, damit der Apicall, weitere Informationen hintelegen kann. Hier ist natürlich eine Klasse bestimmt einfacher und angenehmer. 
Um statische Inhalte einzulesen, gibt man die Variable urlid mit an. Diese wird dann durch die eingeladene JavaScript Datei und darin durch den JavaScript Funktionsaufruf, als Parameter übergeben, so dass man dann die Antwort identifizieren kann. Bei dynamischen Antworten, kann der Server die entsprechenden Parameter setzen, so dass die urlid nicht nötig ist. Dann wird im Header ein Node erzeugt und die Datei eingelesen. Passiert natürlich asynchron. Damit man erkennen kann, ob ein Fehler beim Einlesen passiert ist, zum Beispiel, weil die Datei nicht vorhanden ist, wird eine Timer gesetzt. Der Standardwert ist auf 5 Sekunden gesetzt, das sollte langen, um auch bei langsamen Verbindungen nicht zu früh abzubrechen und gleichzeitig nicht zu spät den Fehler zu erkennen.
Sobald die Datei eingelesen ist, wird der darin enthaltene Code ausgeführt. Darum sollte dieser Code am besten auch den Funktionsaufruf RS_APIDispatcher(id, result) aufrufen. Als Parameter enthalten dann die URLID und als RESULT das entsprechende Ergebnis. Man sollte wissen, was man als Ergebnis erwartet. Es kann auch neben einem JSON auch eine Funktion sein. Diese wird dann auch gleich aufgerufen. So eine Art Callback. So kann man auch vom Server aus, bzw aus der Datendatei eine Funktion erzwingen. Am besten man erstellt ein JSON als result. Das kann man dann in der Callback-Funktion auswerten. Die Fehlerbehandlungsfunktion ist simple. Sollte diese aufgerufen werden, prüft diese ob nicht die Datei eingeladen wurde. Falls nicht, geht sie von einem Fehler aus und entfernt die Variablen zu diesem Apicall.


Die Datendatei
Die Statische Daten-JavaScript-Datei könnte folgenden Inhalt haben.

RS_APIDispatcher("buchdaten/autor", {'name':'detleff egge', 'titel':'mein buch'});


Beispiel Aufruf

var url = "http://api.domain.com/datendatei.js";
apicall(url,
function(h) {
alert(h.object.name);
  }, 
function(h){alert("error");}, 
10000,
"buchdaten/autor"
);


Fazit
Ist diese Methode einmal eingebunden, kann man auch ohne dynamische Server, ein hohes Mass an Dynamik und Flexibilität erreichen. Sind dynamische Server im Einsatz, muss man auf denen die Ausgabe erst implementieren, aber das ist recht einfach. Der Aufruf erfolgt mit dem Namen der Callback-Funktion und der im JavaScript verwalteten ID. So könnte ein dynamisches Skript diese Daten setzen und dann den Inhalt senden.
Diese Apicall-Methoden werden auch von RapidShare und DirectPayLink unterstützt und erlauben somit eigene Applikationen auf HTML und JavaScript Basis.

Keine Kommentare:

Kommentar veröffentlichen