Changes between Initial Version and Version 1 of skript/ajax


Ignore:
Timestamp:
Oct 14, 2018, 11:21:44 AM (6 years ago)
Author:
tracadmin
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • skript/ajax

    v1 v1  
     1= AJAX =
     2Kommunikation zwischen Client und Server soll nicht nur über die Eingabe von URLs in die Adresszeile des Browsers erfolgen, sondern auch aus dem laufenden Clientprogramm heraus initiiert werden können. Dafür wird das Konzept AJAX ('''A'''synchronous '''J'''avaScript '''a'''nd '''X'''ML) verwendet. Das Konzept besagt, dass von !JavaScript-Programmen aus HTTP-Requests asynchron abgesetzt werden. Asynchron bedeutet hierbei, dass der Client nicht blockiert, bis der Server die Anfrage verarbeitet und die Antwort zurückgesendet hat, sondern dass ein Eventhandler registriert wird, der von der Laufzeitumgebung aufgerufen wird, sobald die Antwort des Servers vorliegt.
     3
     4Entgegen der Namensbezeichnung sind wir nicht auf XML als Datenstruktur für die transportierten Daten beschränkt.
     5
     6== Echoserver ==
     7Als einfaches Beispiel dient ein Echoserver, der den [#querystring Querystring] der gesendeten Anfrage unverändert an den Client zurückschickt (Datei [[source:Web_Quellcode_FP/vorlesung/ajax/http_echo.js]]):
     8
     9{{{
     10#!CodeExample
     11## title = Echoserver
     12## repo = Web_Quellcode_FP
     13## path = vorlesung/ajax/http_echo.js
     14#!javascript
     15}}}
     16
     17[=#querystring]
     18Der Echoserver lässt sich testen, indem im Browser ein URL aufgerufen wird, der einen Querystring enthält. Der Querystring ist eine Sammlung von durch `&` getrennten !Schlüssel/Wert-Paaren. Er wird am Ende des URLs, beginnend mit einem `?` angegeben:
     19
     20{{{
     21http://localhost:3000/?key1=hallo&key2=welt
     22}}}
     23
     24Die Antwort des oben angegebenen Beispielservers enthält den Querystring des Requests, der im Browser ausgegeben wird:
     25
     26{{{
     27?key1=hallo&key2=welt
     28}}}
     29
     30== XMLHttpRequest ==
     31Der eigentliche Zweck von AJAX besteht nicht vorrangig darin, ganze HTML-Seiten abzurufen (das kann der Browser auch ohne AJAX), sondern einzelne Datenschnipsel aus dem laufenden Programm heraus an den Server zu senden oder von diesem abzurufen. Dazu dient das [[https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest|XMLHttpRequest-Objekt]] des Browsers.
     32
     33Im Folgenden werden Einzelaspekte aus der Datei [[source:Web_Quellcode_FP/vorlesung/ajax/send_http.html]] diskutiert. Für korrektes Funktionieren der Beispiele muss jQuery eingebunden sein.
     34
     35Zuerst wird ein XMLHttpRequest-Objekt erzeugt:
     36{{{
     37#!javascript
     38var xhttp = new XMLHttpRequest();
     39}}}
     40
     41Bei diesem Objekt registrieren wir den Eventhandler, der darauf reagiert, dass sich der Zustand des Requests (NICHT der HTTP-Status) ändert. Der Name des Eventhandlers ist `onreadystatechange`. Im folgenden Beispiel wird die Antwort des Servers ([[https://wiki.selfhtml.org/wiki/JavaScript/XMLHttpRequest/responseText| xhttp.responseText]]) in ein `div` der Seite geschrieben:
     42{{{
     43#!javascript
     44// Eventhandler: wird immer aufgerufen, sobald der ReadyState des Requests sich ändert
     45xhttp.onreadystatechange = function() {
     46        // Wir interessieren uns nur für ReadyState 4 (Anfrage beendet und Antwort ist bereit)
     47        // und HTTP-Status OK
     48        if (xhttp.readyState === 4 && xhttp.status === 200) {
     49                // Schreibe die Antwort in das HTML-Dokument (DOM)
     50                $('#antwort'.html) = xhttp.responseText;
     51        }
     52};
     53}}}
     54
     55Nachdem der Eventhandler registriert wurde, wird die eigentliche Anfrage an den Server abgesetzt. Die Zufallszahl im Querystring dient nur zur visuellen Überprüfung, ob sich etwas geändert hat:
     56{{{
     57#!javascript
     58// Anfrage an den Server vorbereiten ...
     59xhttp.open("GET", "http://localhost:3000/?key1=hallo&key2=welt&rnd=" + Math.random());
     60
     61// ... und absenden
     62xhttp.send();
     63}}}
     64
     65== Kommunikation mittels JSON ==
     66Zur Kommunikation zwischen Client und Server eignet sich JSON sehr gut, da in den meisten Programmiersprachen APIs zur Kodierung und Dekodierung von JSON enthalten sind. Beispielsweise könnte ein !JavaScript-Client auch ohne besonderen Transformationsaufwand JSON-Objekte an einen PHP-Server senden und von dort empfangen.
     67
     68Zwischen !JavaScript-Client und -Server stellt sich die Frage nach dem Format ohnehin normalerweise nicht, da JSON direkt in Objekte zurücktransformiert werden kann. Der Echoserver kann an die Verarbeitung von JSON angepasst werden:
     69{{{
     70#!javascript
     71// URL parsen
     72var reqdetails = url.parse(req.url, true);
     73console.log(reqdetails);
     74
     75// JSON aus dem Querystring lesen
     76var json = reqdetails.query.obj;
     77// in Objekt umwandeln
     78var o = JSON.parse(json);
     79       
     80// ausgeben
     81console.log(o);
     82       
     83// HTTP Header schreiben
     84// Cross-Site-Scripting erlauben!
     85res.writeHead(200, {
     86        "content-type": "application/json",
     87        "Access-Control-Allow-Origin": "null"
     88});
     89       
     90// eigentliche Ausgabe schreiben
     91// Querystring echoen
     92res.write(JSON.stringify(o));
     93       
     94// abschließen
     95res.end();
     96}}}
     97
     98Der Content-Type der Antwort wurde auf `application/json` gesetzt. Dadurch kann der Browser ggf. die erhaltenen Daten vorverarbeiten.
     99
     100Im Objekt `reqdetails`, das durch Parsen des Objekts `req.url` gewonnen wurde, legt Node.js das Attribut `query` an. Dieses ist ein Objekt, das alle Komponenten des Querystrings als Attribute enthält. Es kann direkt mit den Attributnamen auf die Werte zugegriffen werden, wenn die Namen der Schlüssel des Querystrings bekannt sind. Im Fall oben ist das `obj`.
     101
     102Dieser Name wird in unserem Beispiel dann clientseitig als Schlüssel im Querystring verwendet, womit die Verbindung zum Objekt `reqdetails.query` hergestellt ist. Als Wert wird dann entsprechend der JSON-String eingesetzt:
     103{{{
     104#!javascript
     105function sendRequest() {
     106        // Dummy-Objekt
     107        var o = {
     108                key1: "hallo",
     109                key2: "welt",
     110                rnd: Math.random()
     111        };
     112       
     113        // Anfrage an den Server vorbereiten ...
     114        xhttp.open("GET", "http://localhost:3000/?obj=" + JSON.stringify(o));
     115
     116        // ... und absenden
     117        xhttp.send();
     118}
     119}}}
     120
     121Als Antwort haben wir ebenfalls JSON erhalten. Dieses wird aus dem `responseText` geparst und dann attributweise in das `div` mit der ID `antwort` geschrieben. Hier ist zu beachten, dass das jQuery-Objekt des Div bereits vor dem Betreten der Schleife gespeichert wird, damit nicht bei jedem Schleifendurchlauf ein laufzeitintensives Query gegen das DOM ausgeführt wird:
     122{{{
     123#!javascript
     124xhttp.onreadystatechange = function() {
     125        // Wir interessieren uns nur für State 4 (Anfrage beendet und Antwort ist bereit)
     126        // und HTTP-Status OK (200)
     127        if (xhttp.readyState === 4 && xhttp.status === 200) {
     128                // Schreibe die Antwort in das HTML-Dokument (DOM)
     129               
     130                // JSON parsen
     131                var obj = JSON.parse(xhttp.responseText);
     132
     133                // jQuery-Objekt des div
     134                var div = $('#antwort');
     135                // Attribute des erhaltenen Objekts durchlaufen ...
     136                for(var key in obj){
     137                        // ... und ausgeben
     138                        div.append(key + ": " + obj[key] + "<br>");
     139                }       
     140        }
     141};
     142}}}
     143
     144== Weitere HTTP-Request Typen ==
     145Zur Realisierung von REST-Interfaces sind neben GET die HTTP-Verben POST, PUT und DELETE von Bedeutung. Bei der Verwendung von POST und PUT ist die Nutzlast nicht im URL sondern im Request-Body enthalten. Im Request-Body können nahezu beliebige Datenmengen transportiert werden. Die Verwendung des Request-Body erfordert Anpassungen am Code des Clients. Die Nutzdaten werden nicht mehr an den Request-URL angehängt, sondern als Parameter an die Funktion `send` des XMLHttpRequest-Objekts übergeben (Datei [[source:Web_Quellcode_FP/vorlesung/ajax/send_http_post.html]]):
     146{{{
     147#!javascript
     148function sendRequest() {
     149        // Dummy-Objekt
     150        var o = {
     151                key1: "hallo",
     152                key2: "welt",
     153                rnd: Math.random()
     154        };
     155       
     156        // POST-Anfrage an den Server vorbereiten ...
     157        xhttp.open("POST", "http://localhost:3000/");
     158
     159        // ... und mit der Nutzlast im Request-Body absenden
     160        xhttp.send(JSON.stringify(o));
     161}
     162}}}
     163
     164Serverseitig sind umfassendere Änderungen vorzunehmen. Ab einer bestimmten Größe (die oft nicht vorhersagbar ist), werden POST-Requests in Teilen, sogenannten Chunks, übertragen. Viele Frameworks und Clients verwenden standardmäßig Chunkgrößen von 2, 4 oder 8 KB.
     165
     166Da der Server beim Warten auf Chunks nicht blockieren soll, ist es erforderlich, einen Eventhandler zu registrieren, der beim Eintreffen eines Chunks ausgeführt wird. Das Request-Objekt von Node.js hat die Funktion `on` zum Registrieren von Eventhandlern, welcher mit dem ersten Parameter das zu überwachende Ereignis mitgeteilt wird. Im Fall des Eintreffen eines Chunks wird das Event `data` als Zeichenkette angegeben. Der zweite Parameter ist der eigentliche Eventhandler. Im data-Eventhandler werden die ankommenden Chunks aneinandergekettet:
     167{{{
     168#!javascript
     169var data = "";
     170req.on('data', function(chunk) {
     171    data = data + chunk.toString();
     172});
     173}}}
     174
     175Sobald alle Chunks eingetroffen sind, wird dies durch das Ereignis `end` signalisiert. Im end-Eventhandler wird dann die eigentliche Antwort generiert und an den Client zuückgeliefert. Die vollständige Implementierung des POST-Requests (Datei [[source:Web_Quellcode_FP/vorlesung/ajax/http_echo_post.js]]):
     176{{{
     177#!javascript
     178var data = "";
     179// POST muss gesondert behandelt werden, da die Daten in Chunks kommen können
     180if (req.method === "POST") {
     181        console.log("[200] " + req.method + " to " + req.url);
     182        // Das Request-Objekt ist da, sobald die Header vollständig sind, die Daten
     183        // sind aber evtl. noch nicht da --> Eventhandler
     184
     185        // Es kommen Daten: Eventhandler für Chunks
     186        req.on('data', function(chunk) {
     187                data = data + chunk.toString();
     188        });
     189               
     190        // Der Request ist beendet: Eventhandler sendet die Antwort an den Client
     191        req.on('end', function() {
     192                // empty 200 OK response for now
     193                res.writeHead(200, "OK", {
     194                        "Content-Type": "application/json",
     195                        "Access-Control-Allow-Origin": "null"                     
     196                });
     197                       
     198                // Die erhaltenen Daten zerlegen und anzeigen
     199                var o = JSON.parse(data);
     200                for(var key in o){
     201                        console.log(key + ": " + o[key]);
     202                }
     203                       
     204                // zurückschicken
     205                res.write(JSON.stringify(o));
     206               
     207                res.end();
     208        });     
     209} else {
     210        // Kein POST-Request
     211        // ...
     212}
     213}}}
     214
     215Diese Konstruktion wird leicht unübersichtlich. Daher empfiehklt es sich, Codeteile in eigene Funktionen (z. B. `handleGet`, `handlePost`usw.) auszulagern.
     216
     217== HTTP-Requests mit jQuery absetzen ==
     218Neben der Manipulation des DOM gestattet jQuery einen [[https://api.jquery.com/category/ajax/|komfortablen Umgang mit dem XMLHttpRequest-Objekt]]. Insbesondere kapselt es das Attribut `onreadystatechange` und erlaubt die Definition von vereinfachten Eventhandlern für verschiedene Requesttypen ([[https://api.jquery.com/jQuery.get/|GET]], [[https://api.jquery.com/jQuery.post/|POST]], [[https://api.jquery.com/jQuery.ajax/|Weitere]]).
     219
     220Durch die Verwendung von jQuery zum clientseitigen Absetzen des POST-Requests verienfacht sich die Implementierung des Clients deutlich (Datei [[source:Web_Quellcode_FP/vorlesung/ajax/send_http_post_jquery.html]]). jQuery profitiert dabei von der serverseitigen Einstellung des Content-Type auf `application/json`. Dadurch wird die Antwort direkt geparst und als Objekt im Parameter `result` des Eventhandlers abgelegt:
     221
     222{{{
     223#!CodeExample
     224## title = POST-Request per jQuery absetzen
     225## repo = Web_Quellcode_FP
     226## path = vorlesung/ajax/send_http_post_jquery.html
     227#!html
     228}}}