# Asynchronous browser/server interaction This material is part of [web development courses](https://github.com/MediaComem/comem-webdev) for [Media Engineering](https://heig-vd.ch/formations/bachelor/filieres/ingenierie-des-medias). We will talk about a technology called **AJAX (Asynchronous JavaScript and XML)** which allows in some ways to improve the user interface behaviour with a good effect on the user experience. **You will need** * [Google Chrome][chrome] (recommended, any browser with developer tools will do) * [Sublime Text][sublime] (recommended, any code editor will do... **except Notepad**) **Recommended reading** * [Project setup][projset] * [Bootstrap][bootstrap] * [JavaScript][js-bas] * [DOM API][js-dom] * [jQuery basics][jq-dom] --- ## AJAX technology .breadcrumbs[
Asynchronous browser/server interaction
] From "classical" web sites (~ 1.0) to "desktop-like" **web applications** (~ 2.0) * Exchange data between browser and server * raw text * HTML * XML, **JSON**, ... * Using asynchronous techniques * Dynamic Script Loading * **XMLHttpRequest** * With JavaScript to orchestrate the communication > Also known as **Single Page Application** --- ## Classic model .breadcrumbs[
Asynchronous browser/server interaction
]
> The page is **fully reconstructed and reloaded** to update the content (quite inefficient) and the user activity is **interrupted, waiting** the end of the action request
--- ## Asynchronous model .breadcrumbs[
Asynchronous browser/server interaction
]
> Only **update a part** of the content and the user activity is **not interrupted** (continue to play with UI while update is in progress)
> The AJAX User eXperience is a key concept of the [web as a platform](https://platform.html5.org/) --- class: center, middle ## Starting file .breadcrumbs[
Asynchronous browser/server interaction
] This subject will use [this `index.html` file][ajsf] as illustration. Be sure to download it, if you want to try and follow with the examples. The basic idea of the following examples is about a user interface allowing to choose among a list of airports of Switzerland so as to get weather observation each time the user click on the button. --- ## Dynamic Script Loading .breadcrumbs[
Asynchronous browser/server interaction
] At first we need to add some interaction with the button, calling a function named `send`, for example like this: ```js document.onreadystatechange = function () { if (document.readyState === "complete") { var btn = document.getElementById("get-observation"); btn.addEventListener("click", send); } }; ``` > You can also decide to load the JavaScript at the end of the body ... --- ## Dynamic Script Loading .breadcrumbs[
Asynchronous browser/server interaction
] Now it's time for a first asynchronous request like this: ```js function send() { var serviceURL = "http://dfa-ogo.rhcloud.com/getWeatherIcaoHTML.php"; var code = document.getElementById("airportCode").selectedOptions[0].value; serviceURL+= "?icao=" + code; var DSLScript = document.createElement("script"); DSLScript.src = serviceURL; DSLScript.type = "text/javascript"; document.body.appendChild(DSLScript); document.body.removeChild(DSLScript); document.getElementById("status").childNodes[0].data = "Waiting ..."; } ``` * we compose the **web service URL** call with a **GET parameter** * we **insert a script element** so as to trigger the loading request * given that Dynamic script loading is asynchronous, the code **execution can go on**, so we insert a status message in the UI --- class: center, middle ## Web service, web resource, URL, HTTP GET, POST, ... .breadcrumbs[
Asynchronous browser/server interaction
] Let's already have a look at these slides: [https://mediacomem.github.io/comem-webdev-docs/2017/subjects/rest/#10](https://mediacomem.github.io/comem-webdev-docs/2017/subjects/rest/#10) --- ## Web resource `getWeatherIcaoHTML` .breadcrumbs[
Asynchronous browser/server interaction
] Let's click the button and nothing happens, except ... * we can see that a GET request **in the background** `http://dfa-ogo.rhcloud.com/getWeatherIcaoHTML.php?icao=LSZB` * we can see an **error** `ReferenceError: callback is not defined` * we can see the result of the GET request (a **JavaScript function call**) ```js callback('
Bern / Belp
16
29
clouds and visibility OK
') ``` > Something is missing! > We need to declare a callback function ... --- ## Callback function declaration .breadcrumbs[
Asynchronous browser/server interaction
] This is the function which the loaded instruction does order to call ```js function callback(sMessage) { var info = document.getElementById("tableInfo"); info.insertAdjacentHTML("beforeend", sMessage); document.getElementById("status").childNodes[0].data = "Done"; } ``` * as soon as **the script loaded is executed** by the browser (because it was insert in a ` ``` > Then register a click event listener on the button ```js $(document).ready(function (e) { $("#get-observation").click(send); }); ``` > Let's add an AJAX request: * there is the [main method][jqajdoc] `jQuery.ajax()` or `$.ajax()` which offers all the possible functionalities. * there are also [shorthand methods][smsdoc] for the more common and specific types of AJAX requests with even less code - `jQuery.get(), jQuery.getJSON(), jQuery.getScript(), jQuery.post(), .load()`. --- ## Use of `$.ajax()` .breadcrumbs[
Asynchronous browser/server interaction
] The send() function now looks like this ```js function send() { $.ajax({ url: "http://dfa-ogo.rhcloud.com/getWeatherIcaoJSON.php", method: "GET", data: { icao: $("option:selected").val() }, dataType: "json" }).done(callbackOk); $("#status").text("Waiting ..."); } ``` * `$.ajax` takes an object - it holds properties to configure the AJAX request * the service **URL** and **HTTP method** * the data object whose **keys/values** are used to complete the request * the expected **data type** of the result * we register the `done` **function callback** which is called when the request terminates successfully. --- ## `done` callback function .breadcrumbs[
Asynchronous browser/server interaction
] Here is the callback function ```js function callbackOk(weatherInfo) { var arrayInfo = $.map(weatherInfo, function (ele) { return ele; }); displayInfo(arrayInfo); $("#status").text("Done"); } ``` * jQuery does the parsing for us because the request is set with `dataType: "json"` * so we receive the result straightforwardly as a JavaScript object * we use the $.map function to convert the object into an array ... why? see next ... --- ## Display the result .breadcrumbs[
Asynchronous browser/server interaction
] Finally we need a display function in charge of updating the UI ```js function displayInfo(info) { var newtr = $(".hidden").clone(); $(newtr).removeClass("hidden"); $(newtr).children().each(function (i) { $(this).text(info[i]); }); $("tbody").append(newtr); } ``` * The info array is here useful to associate each value to a cell with a loop > The idea is to do post-processing of the result apart from the display --- ## Now, let's make it fail! .breadcrumbs[
Asynchronous browser/server interaction
] We (I, in fact) remove this configuration from the `dfa-ogo.rhcloud.com` server (see also [Dis-E-nable CORS on Apache](https://enable-cors.org/server_apache.html)) `Header set Access-Control-Allow-Origin "*"` > Just try now to get an observation ... > Nothing works anymore :-( ... and the console says something like `"Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://dfa-ogo.rhcloud.com/... (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)."` So, there is something to understand about: * SOP, which stands for **Same Origin Policy** * CORS, which stands for **Cross-Origin Resource Sharing** --- ## Same-origin policy (SOP) .breadcrumbs[
Asynchronous browser/server interaction
] Since the mid of nineties: * important concept for web app security to **protect access to the DOM** * browser permits scripts contained in a first web page to access data in a second web page, but **only if both web pages have the same origin** * origin is the triple **{protocol, host, port}** `http://www.mas-rad.ch/programme/cas-dar.aspx`
`http://www.mas-rad.ch/contact.aspx`
are of same origin (same protocol, host and port) `http://www.mas-rad.ch/programme/cas-dar.aspx`
`https://cyberlearn.hes-so.ch/enrol/index.php?id=6704`
are not of same origin (different protocol, different host) `http://blog.cyberlearn.ch/?p=2876`
`http://www.cyberlearn.ch`
are not of same origin (different host) --- ## Why does SOP protect the final user? .breadcrumbs[
Asynchronous browser/server interaction
] What if Same Origin Policy was not the default behaviour? * document.cookie is often used to authenticate sessions * given a final user visiting a banking website and does forget to log out * malicious JavaScript code running by another visited web page (e.g. in another tab) can do anything the user could do on the banking site * i.e. send requests to the banking site with the banking site's session cookie (e.g. get a list of transactions) > That would be really bad! Same Origin Policy does apply on: * cookies * **AJAX** request (using XMLHTTPRequest object) * DOM access * data storage access (e.g. localStorage) --- ## Relax it for AJAX ... using CORS .breadcrumbs[
Asynchronous browser/server interaction
] Sometimes the same-origin policy is too restrictive * many web applications require to interact with different origins through cross-origin requests * as soon as these **origins are trusted**, why would'nt it be possible? * by the way, the Dynamic Script Loading is not affected by the same-origin policy! So it is already possible! **Cross-Origin Resource Sharing (CORS)** * recommended standard of the **W3C** to relax SOP * more **secure** than simply allowing all cross-origin requests (SOP is still the default behaviour) * browser/server interaction to decide if cross-origin request is safe or not * the server does decide to **allow or not the cross-origin request**
--- ## Simple CORS example .breadcrumbs[
Asynchronous browser/server interaction
] In context of a cross-origin request from a page loaded from server A : * browser sends to server B a HTTP `OPTIONS` request with the following header `Origin: http://the.domain.of.server.A` * server at `http://the.different.domain.of.server.B` may answer `Access-Control-Allow-Origin: *` * means the cross-origin request is allowed, let's provide the data to the browser > So, let's rollback my server config ... and do something to handle a failure! --- ## `fail` callback function .breadcrumbs[
Asynchronous browser/server interaction
] Beside the `done` callback, we register the `fail` **function callback** which is called when the request does fail. * Complete the `$.ajax` call like this ```js $.ajax({ // usual config options }).done(callbackOk).fail(callbackFail); ``` * And add the related function ```js function callbackFail(xhr) { $("#status").text("Failed :-("); } ``` > See also the [jqXHR object](http://api.jquery.com/jQuery.ajax/#jqXHR) returned by $.ajax --- ## Now it's your turn ... .breadcrumbs[
Asynchronous browser/server interaction
]
Use this crazy API [https://api.chucknorris.io](https://api.chucknorris.io): * `https://api.chucknorris.io/jokes/random` * `https://api.chucknorris.io/jokes/search?query={query}` Requirements: * when loading the web application, a random fact is displayed on the top * when the search button is clicked the results of a free text search are piled up on the bottom according to the text entered by the user --- ## Resources .breadcrumbs[
Asynchronous browser/server interaction
] You will find the final HTML file for this course here **Documentation** * [XMLHttpRequest Web API][xhr] * [jQuery AJAX documentation][jqajdoc] * [Same Origin Policy MDN documentation][sop] * [CORS W3C recommandation][cors] * [GeoNames Weather JSON Webservice][geonames] [bootstrap]: ../bootstrap [js-bas]: ../js [js-dom]: ../js-dom [jq-dom]: ../jquery-dom [projset]: ../masrad-project-setup [chrome]: https://www.google.com/chrome/ [sublime]: https://www.sublimetext.com/ [ajsf]: https://gist.githubusercontent.com/oertz/f5b661e075aa59a326b2d56a4567495d [fef]: https://gist.githubusercontent.com/oertz/164a883774727e34fd9190e6abf84bd2/raw/95374b82a1fd742538dea9f72d9443d3dee5e08d/index.html [xhr]: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest [smsdoc]: https://api.jquery.com/category/ajax/shorthand-methods/ [jqajdoc]: https://api.jquery.com/jQuery.ajax/ [cors]: https://www.w3.org/TR/cors/ [sop]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy [geonames]: http://www.geonames.org/export/JSON-webservices.html#weatherIcaoJSON