| 1 | = AJAX = |
| 2 | Kommunikation 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 | |
| 4 | Entgegen der Namensbezeichnung sind wir nicht auf XML als Datenstruktur für die transportierten Daten beschränkt. |
| 5 | |
| 6 | == Echoserver == |
| 7 | Als 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] |
| 18 | Der 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 | {{{ |
| 21 | http://localhost:3000/?key1=hallo&key2=welt |
| 22 | }}} |
| 23 | |
| 24 | Die 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 == |
| 31 | Der 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 | |
| 33 | Im 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 | |
| 35 | Zuerst wird ein XMLHttpRequest-Objekt erzeugt: |
| 36 | {{{ |
| 37 | #!javascript |
| 38 | var xhttp = new XMLHttpRequest(); |
| 39 | }}} |
| 40 | |
| 41 | Bei 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 |
| 45 | xhttp.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 | |
| 55 | Nachdem 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 ... |
| 59 | xhttp.open("GET", "http://localhost:3000/?key1=hallo&key2=welt&rnd=" + Math.random()); |
| 60 | |
| 61 | // ... und absenden |
| 62 | xhttp.send(); |
| 63 | }}} |
| 64 | |
| 65 | == Kommunikation mittels JSON == |
| 66 | Zur 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 | |
| 68 | Zwischen !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 |
| 72 | var reqdetails = url.parse(req.url, true); |
| 73 | console.log(reqdetails); |
| 74 | |
| 75 | // JSON aus dem Querystring lesen |
| 76 | var json = reqdetails.query.obj; |
| 77 | // in Objekt umwandeln |
| 78 | var o = JSON.parse(json); |
| 79 | |
| 80 | // ausgeben |
| 81 | console.log(o); |
| 82 | |
| 83 | // HTTP Header schreiben |
| 84 | // Cross-Site-Scripting erlauben! |
| 85 | res.writeHead(200, { |
| 86 | "content-type": "application/json", |
| 87 | "Access-Control-Allow-Origin": "null" |
| 88 | }); |
| 89 | |
| 90 | // eigentliche Ausgabe schreiben |
| 91 | // Querystring echoen |
| 92 | res.write(JSON.stringify(o)); |
| 93 | |
| 94 | // abschließen |
| 95 | res.end(); |
| 96 | }}} |
| 97 | |
| 98 | Der Content-Type der Antwort wurde auf `application/json` gesetzt. Dadurch kann der Browser ggf. die erhaltenen Daten vorverarbeiten. |
| 99 | |
| 100 | Im 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 | |
| 102 | Dieser 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 |
| 105 | function 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 | |
| 121 | Als 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 |
| 124 | xhttp.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 == |
| 145 | Zur 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 |
| 148 | function 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 | |
| 164 | Serverseitig 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 | |
| 166 | Da 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 |
| 169 | var data = ""; |
| 170 | req.on('data', function(chunk) { |
| 171 | data = data + chunk.toString(); |
| 172 | }); |
| 173 | }}} |
| 174 | |
| 175 | Sobald 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 |
| 178 | var data = ""; |
| 179 | // POST muss gesondert behandelt werden, da die Daten in Chunks kommen können |
| 180 | if (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 | |
| 215 | Diese 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 == |
| 218 | Neben 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 | |
| 220 | Durch 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 | }}} |