Fork me on GitHub

Web Development

Ein Lehrbuch für das Informatik oder Medien-Informatik Studium.

Wir kennen schon die Funktionsweise von HTTP. Bisher wurde ein HTTP Request durch eine Handlung der UserIn ausgelöst (URL eintippen, Link anklicken), oder um Ressourcen zu laden die zu einem HTML-Dokument gehören.

Mit AJAX lernen wir nun eine neue Art kennen, wie HTTP-Request verwendet werden: Asynchrone Requests.

Was ist AJAX?

AJAX ist die englische Abkürzung für „Asynchrones Javascript und XML“. In diesem Kapitel lernen Sie was das genau bedeutet, und dass sich hinter dem X zum Schluss auch andere Format verbergen können

Ein Beispiel für die Verwendung von AJAX ist das in der Abbildung unten gezeigte Eingabefeld: schon während des Eintippens eines Suchwortes wird eine Anfrage an den Webserver geschickt. Dieser antwortet mit einer Liste von vorgeschlagenen Namen. Diese Liste wird mit Javascript in einer div unterhalb des Eingabefelds angezeigt:

Abbildung 50: Vorschläge für die Eingabe werden über AJAX geladen

Mit AJAX wird hier eine HTTP-Anfrage gesendet.

Bei einer „normalen“ HTTP-Anfrage schaltet der Browser auf „Warten“, eine neue vollständige Webseite wird geladen und angezeigt.

Asynchron heisst hier: der Request wird abgesetzt, das Javascript-Programm läuft sofort weiter, die UserIn kann weiterhin mit der Webseite interagieren. Erst wenn die Antwort des Servers vorliegt wird die normale Darstellung der Seite kurz unterbrochen und ein Javascript-Programm fügt die Daten in die Seite ein.

Im Javascript Programm

Auf der Ebene des Javascript Programm-Codes sieht der Unterschied zwischen synchron und asynchron so aus:

Javascript Code synchron

Befehl1();
Befehl2();
Antwort = synchron_laden(url);   // dauert ewig 
Befehl3();                      // viel später
Befehl4();

Bevor Befehl3 ausgeführt werden kann muss erst die Antwort des Servers vorliegen – hier kann also eine Wartezeit von mehreren Sekunden entstehen.

Javascript Code asynchron

function handle_data(Antwort) {  
   ... 
} 
 
Befehl1();
Befehl2();
asynchron_laden(url, handle_data);  // dauert kurz 
Befehl3();                         // kurz darauf
Befehl4();

Befehl3 und Befehl4 können sofort ausgeführt werden, egal ob und wie schnell der Server antwortet. Das Javascript-Programm (befehle 1 bis 4) läuft vollständig ab.

Wenn die Daten vom Server schließlich einlangen wird die Funktion handle_data aufgerufen und die Daten zu verarbeiten.

HTTP

Betrachten wir nun den Ablauf für ein Textfeld mit Autocomplete-Funktion, wie in der obigen Abbildung gezeigt. Folgende Abbildung ist ein Sequenz Diagramm, die Zeit läuft von oben nach unten.

Zuerst wird die Webseite mit dem Formular geladen: der Browser schickt die Anfrage an den Server und erhält eine Antwort. Was immer zuvor im Browser angezeigt wurde wird - nach Ankunft des HTTP Response - gelöscht, die neue Seite wird im Browser dargestellt. Diese Verhalten des Browsers ist uns schon bekannt.

Nun kommt der neue Teil: das Eintippten des ersten Buchstabens ins Eingabefeld löst ein Javascript-Programm aus, das einen AJAX-Request absetzt. Am Netzwerk ist das ein ganz normaler HTTP Request, für den Server gibt es keinen Unterschied zu jedem anderen Request.

Was anders ist, ist das Verhalten des Browsers: Das Absenden des Requests bleibt die Webseite bestehen und bleibt interaktiv - das Absenden passiert meist von der UserIn unbemerkt.

Wenn die Daten des Response einlangen wird nicht die Seite gelöscht, sondern es wird eine Javascript-Funktion in der Seite aufgerufen, die die Daten entgegen nimmt. Für das Autocomple-Verhalten bestehen die Daten aus einer Liste von Vorschlägen, die Javascript-Funktion zeigt diese Vorschläge unterhalb des Eingabefeldes an.

AJAX Ablauf

Datenformate - mehr als nur XML

Das X am Ende von AJAX steht für XML – das stimmt aber nicht: die Daten vom Server können im XML-Format gesendet werden, aber genauso auch als HTML oder reiner Text oder JSON. Man könnte das X in AJAX auch als „X-beliebiges Format“ deuten.

Simples Javascript Beispiel

Im ersten AJAX Beispiel wird der Output eines PHP-Counters in eine HTML-Seite eingebunden.

Html Code Counter einbinden mit Javascript

<html>
<head>
  <meta charset="utf-8">
  <title>AJAX counter</title>
</head>
<body>
  <h1>Webseite</h1>

  <p>mit total viel Inhalt</p>

  <script>
    function handleCounterData() {
      console.log("Response wurde empfangen");
      let counter = this.responseText;
      // counter enhält jetzt den output des php programms
    }

    let ajaxRequest = new XMLHttpRequest();
    ajaxRequest.addEventListener("load", handleCounterData);
    ajaxRequest.open("GET", "counter_ajax.php");
    ajaxRequest.send(); 
    console.log("abgesendet, sofort weiter";   
  </script>
</body>
</html>

Das XMLHttpRequest Objekt liefert verschiedene Events, hier wird nur für das load Event eine Funktion als Listener angebracht. Mit der open Methode wird der HTTP-Request konfiguriert, aber erst mit send wirklich abgeschickt. Da er Request asynchron erfolgt geht der Javascript-Interpreter sofort von send weiter zu console.log in der nächsten Zeile.

Erst sehr viel später, wenn der HTTP-Response vorliegt, wird die Funktion handleCounterData aufgerufen. Die Funktion erhält das XMLHttpRequest Objekt in der Variable this und kann aus this.responseText die Antwort auslesen.

Fetch und Promises

In modernem Javascript, in allen Browsern ausser Internet Explorer, gibt es eine neue Schreibweise für AJAX-Request mit dem Befehl fetchmdn.

Html Code Counter einbinden mit Javascript

<html>
<head>
  <meta charset="utf-8">
  <title>AJAX counter</title>
</head>
<body>
  <h1>Webseite</h1>

  <p>mit total viel Inhalt</p>

  <script>
    fetch("counter_ajax.php")
      .then(function(response) {
        console.log("Response wird empfangen");
        let counter = response.text();
        return counter;
      })
      .then(function(counter) {
        console.log("counter wurde aus dem Response herausgelesen");
        // ...
      }); 
  </script>
</body>
</html>

Mit fetch wird die Reihenfolge des Ablaufs klarer, aber der Ablauf wird auch komplizierter: ein weiterer asynchroner Verarbeitungsschritt kommt dazu: das auslesen des Textes aus dem Response.

Promises

Der Rückgabewert der funktion fetch ist eine Promise: ein Objekt, das den Umgang mit einer asynchrone Operation einfacher machen soll. Es ist ein Platzhalter für das Ergebnis der Operation, das noch nicht bekannt ist. In anderen Programmiersprachen ist die Promise auch als Future oder Deferred bekannt, siehe wikipedia.

Mit der Methode then() kann eine Funktion als Callback angegeben werden, die aufgerufen wird wenn das Ergebnise vorliegt:

fetch("counter_ajax.php")
  .then(function(response) {
    console.log("Response wird empfangen");
    // tu ws mit response response
  })

Chaining

Angenommen mit dem Ergebnis einer asynchronen Operation soll eine weitere asynchrone Operation aufgerufen werden.

Mit Promises funktionert das mittels “aneinanderhängen” mit then:

fetch("counter_ajax.php")
  .then(function(response) {
    console.log("Response wird empfangen");
    let counter = response.text();
    return counter;
  })
  .then(function(counter) {
    console.log("counter wurde aus dem Response herausgelesen");
    // ...
  }); 

Dieser Code kann mit Arrow-Functions noch kürzer werden:

fetch("counter_ajax.php")
  .then(response => response.text())
  .then(function(counter) {
    console.log("Text wurde aus dem Response herausgelesen");
    // tu was mit counter
  });

Fehlerbehandlung

Für die Fehlerbehandlung gibt es zwei Schreibweisen, die üblichere ist die Verwendet von catch:

fetch("counter_ajax.php")
  .then(response => response.text())
  .then(function(counter) {
    console.log("Text wurde aus dem Response herausgelesen");
    // tu was mit counter
  }).catch(error) {
    console.log(error);
  };

Aber Achtung: wenn der HTTP-Response z.B. 404 oder 500 ist, löst das noch keinen Error aus, der mit catch gefangen werden kann. Das müsste man selbst behandeln:

fetch("counter_ajax.php")
  .then(function(response){ 
    if (response.status !== 200) {
        throw new Error("Not 200 response");
    } 
    return response.text(); 
  }).then(function(counter) {
    console.log("Text wurde aus dem Response herausgelesen");
    // tu was mit counter
  }).catch(error) {
    console.log(error);
  };

Siehe auch

Autocomplete

Wir werden in diesem Beispiel ein Autocomplete-Feld bauen. Beginnen wir mit dem Backend:

Backend

Am Server befindet sich eine Datenbank mit ca. 170.000 Städten. Mit der Anfrage

search.php?term=w

Werden nur die Städte die mit w beginnen geladen, und als JSON-Array zurück gegeben:

[
    "Wa,GH",
    "WaKeeney,US",
    "Waabs,DE",
    "Waaia,AU",
    "Waajid,SO",
    "Waake,DE",
    "Waakirchen,DE",
    "Waal,DE",
    ...
    "Wüstenzell,DE",
    "Wüstheuterode,DE",
    "Wāhan Murad,PK",
    "Wān Yampēng,MM",
    "Wŏnsŏngil-tong,KR",
    "Włocławek,PL",
    "Włodawa,PL",
    "Włoszczowa,PL",
    "Wāsiţ,EG",
    "Wąwolnica,PL"
]

Das sind ca. 5000 Namen.

Beim Lesen aus der Datenbank wird ein Volltext-Index verwendet (siehe Datenbank optimieren: Indexes). Das sieht man aber weder der SQL-Query noch dem PHP an:

$term = filter_var($_GET['term'], FILTER_SANITIZE_STRING) . '%';
$sth->execute(array($term));
$cities = $sth->fetchAll(PDO::FETCH_COLUMN);

Der Output des PHP-Programmes ist JSON. Das muss mit dem HTTP Header Content-Type angekündgt werden:

header('Content-Type: application/json');
echo json_encode($cities);

Frontend

Für das Frontend kann man eine fertige Library verwenden, z.B. JavaScript-autoComplete:

new autoComplete({
    selector: '#cityinput',
    source: function(term, handle_response){
      // schicke suchwort 'term' ans backend
      // wenn die datenvorliegen, rufe die funktion handle_response auf           
    }
});

AJAX Beispiel mit API

In diesem Beispiel werden Wetter-Daten von zwei Quellen angezeigt. Dabei sieht man einen wichtigen Unterschied:

  • auf http://openweathermap.org/ ist der Zugriff nur mit API key möglich, auch vom frontend aus
  • auf http://at-wetter.tk/ ist der Zugriff auch ohne API key möglich, aber nicht von einem fremden Frontend aus, weil CORS nicht erlaubt ist.

Direkter Zugriff auf eine fremde API

Um die API von http://openweathermap.org/ zu benutzen ist eine Anmeldung und ein API key notwendig. Das ermöglicht eine Beschränkung der Zugriffe: am Server kann mitgezählt werden mit welchem API Key wie viele Zugriffe erfolgt sind, und je nach dem limitiert oder verrechnet werden. Die Preise für die API sind nach Anzahl der Zugriffen gestaffelt, im Mai 2017 waren die Preise:

Preise von openweathermap.org

Beim Zugriff auf die API muss jeweils der API-Key als parameter mit gesendet werden:

Javascript Code Zugriff auf die openweathermap API

fetch("http://api.openweathermap.org/data/2.5/weather?&units=metric&q=London,uk&apikey=....")
.then(function...

Die genaue Struktur der Daten und wie man sie zerlegt kann man entweder der Dokumentation entnehmen, oder einfach in der console erforschen.

ABER ACHTUNG: diese API ist (gratis) nur über http zugänglich. Die resultierende Webseite kann wieder nur auf http veröffentlicht werden, nicht auf https.

Um die openweahtermap api auch über https verwenden zu können ist die nächste Lösung notwendig:

Zugriff auf eine API über lokales Backend

Manchmal kann man nicht vom Frontend direkt auf die API zugreifen.

Einen Grund haben wir schon bei openweathermap gesehen: die API ist über http zugänglich, das frontend wird auf https gehosted. So ist es verboten auf die API zuzugreifen.

Der zweite mögliche Grund ist CORS. Das tritt zum Beispiel bei der API at-wetter.tk auf. Die Abfrage scheitert ohne sichtbare Fehlermeldung. In der console wird in manchen Browsern eine Meldung angezeigt:

CORS Fehlermeldung

In beiden Fällen ist die Lösung dieselbe: man muss die Daten über das eigene Backend laden.

In PHP ist der Zugriff auf die API ohne Problem möglich:

Php Code zugriff auf die wetter-at.tk API

header('Content-Type: application/json');
...
$url = "http://at-wetter.tk/api/v1/station/11150/t/$date/7";
$text=file_get_contents( $url );
...

Ausblick

Das waren nur einige wenige Anwendungsbeispiele für AJAX, es gibt natürlich noch viel mehr.

Aber bevor man sich in AJAX Abenteuer stürzt sollte man sich auch über die Probleme bewusst sein, dazu mehr im nächsten Kapitel.