Opisując metody tworzenia RESTful API nie możemy zapomnieć o klientach. Wszystkie ciekawe nagłówki i metody HTTP powinniśmy móc wywołać z poziomu przeglądarki. Dlatego dziś zajmiemy się korzystaniem z REST API w JavaScripcie.
Do testowania napiszmy sobie w PHP mały skrypt logujący nasze wywołania:
<?php file_put_contents("log.txt",$_SERVER['REQUEST_METHOD']."\n",FILE_APPEND);
Od tego momentu możemy monitorować w konsoli zawartość pliku log.txt choćby za pomocą „tail -f„. Możemy przejść do Javascriptu.
Obiekt XMLHttpRequest
Do wywołań AJAX korzystamy z obiektu XMLHttpRequest. Przeważnie podajemy w nim adres i otrzymujey z powrotem dane. Ale to nie wszystko. W metodzie open() możemy skorzystać z możliwości podania metody HTTP. Możemy więc napisać przykładowy kod:
<script type="text/javascript"> var xhr = new XMLHttpRequest(); xhr.open("POST", "http://localhost/index.php"); xhr.send(); </script>
Jeśli wywołamy go w przeglądarce nasz serwer powiadomi nas o wywołaniu wypisując nazwę metody:
POST
Serwer mówi, że nie obsługuje metody POST ale jednak ją dostał. W podobny sposób możemy korzystać z pozostałych metod HTTP. Wyjątkiem są metody CONNECT, TRACE i TRACK, które raportują w przeglądarce błąd (a więc żądania nie docierają do serwera):
SecurityError: The operation is insecure. xhr.open("TRACE", "http://localhost:8000/");
Niektóre metody, te mniej znane mogą nie być rozumiane przez przeglądarkę. W takim przypadku przeglądarka, nie wiedząc czy może wywołać tą metodę wysyła wpierw zapytanie OPTIONS. Musimy zatem uzbroić nasz serwer w obsługę tej metody.
A czy możemy wysłać inne, własne nazwy metod? Możemy. Nic nie stoi na przeszkodzie aby wpisać w nazwie dowolny tekst:
xhr.open("FIRE", "http://localhost/index.php");
Serwer przyjmuje metodę FIRE raportując ją w pliku.
Wysyłamy nagłówki HTTP
Na początek rozszerzmy nasz kod PHP o raportowanie przesłanych nagłówków. Dodamy kod:
$headers = apache_request_headers(); foreach ($headers as $header => $value) { file_put_contents("log.txt","$header: $value\n",FILE_APPEND); }
W obiekcie XMLHttpRequest możemy dodawać dowolne nagłówki do wysłania. W poprzednim kodzie JavaScript przed wysłaniem żądania wstawmy kod:
xhr.setRequestHeader("Naglowek","Dowolny tekst");
Jeśli teraz uruchomimy nasz JavaScript otrzymamy w logu informację podobną do tej:
FIRE Host: localhost User-Agent: Mozilla/5.0 ... Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: pl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Naglowek: Dowolny tekst Referer: http://localhost/index.html Connection: keep-alive Pragma: no-cache Cache-Control: no-cache
Widzimy, że przeglądarka wysłała zestaw swoich nagłówków a dodatkowo dołączyła nasz. Wywołując metodę setRequestHeader() kilkukrotnie możemy dodać dowolne nagłówki.
Zmiana nagłówków przeglądarki też jest możliwa. Wystarczy po prostu ustawić dowolny nagłówek, który normalnie wysyła przeglądarka. Przykładowo ustawiamy typ danych:
xhr.setRequestHeader("Accept","text/plain");
Ten nagłówek zostanie wysłany zamiast domyślnego.
Czytanie nagłówków HTTP
Skoro tak łatwo możemy wysyłać nagłówki, równie łatwe powinno być ich odczytywanie. Tak jest w rzeczywistości. Do przeczytania nagłówków mamy dostępne dwie metody. Pierwsza: getResponseHeader(name) przyjmuje jako parametr etykietę nagłówka, który chcemy pobrać. Druga metoda getAllResponseHeaders() zwraca nam wszystkie nagłówki jako jeden tekst. Spróbujmy użyć ich w JavaScript:
var xhr = new XMLHttpRequest(); xhr.open("GET", "http://localhost:8000/"); xhr.onload = function(e) { var headers = document.querySelector("#headers"); headers.innerHTML = this.getAllResponseHeaders(); }; xhr.send();
Jeśli będziemy mieć w kodzie HTML element z identyfikatorem „headers”, np. element <pre>, zobaczymy w nim wszystkie nagłówki, które zwróci nam serwer. Przykładowo:
Date: ... Server: Apache X-Powered-By: PHP/5.5.10 Keep-Alive: timeout=5, max=98 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: image/jpeg
Praktyczne zastosowanie
W poprzednim artykule przy okazji metody LINK wspomniałem o możliwości stworzenia galerii zdjęć. Obrazy same mogą informować o adresie poprzedniego i następnego zdjęcia za pomocą nagłówków. Czas wdrożyć to rozwiązanie w życie.
Na początek stwórzmy kod PHP prezentujący fotografię, np w pliku image.php:
<?php // linki do poprzedniego i następnego zdjęcia wysyłamy w nagłówku "link" $prev = "http://localhost/image0.jpg"; $next = "http://localhost/image2.jpg"; header("Link: $prev;rel=prev,$next;rel=next"); // nagłówek i dane bieżącego zdjęcia header("Content-type: image/jpeg"); echo file_get_contents("image.jpg");
Jeśli teraz otworzymy w przeglądarce adres naszego pliku, zobaczymy po prostu fotografię. Sama przeglądarka ignoruje nagłówek „Link” bo nie wie co z nim zrobić. Możemy jednak obsłużyć go w AJAXie.
Javascript nie jest dobrze znany ze swej obsługi danych binarnych. Możemy jednak skorzystać z ustawienia responseType, którym wymusimy otrzymanie obiektu BLOB zamiast XML. Oto kod Javascript:
var xhr = new XMLHttpRequest(); xhr.open("GET", "http://localhost/image.php"); xhr.responseType = "blob"; xhr.onload = response; xhr.send();
Cała obsługa odbywa się w funkcji response(), która tworzy obiekt z obrazkiem oraz pobiera linki:
function response(e) { // tworzymy obrazek i dodajemy go do strony var urlCreator = window.URL || window.webkitURL; var imageUrl = urlCreator.createObjectURL(this.response); document.querySelector("#photo").src = imageUrl; // pobieramy linki i parsujemy zwrócony tekst var links = this.getResponseHeader("Link").split(","); for (var link in links) { var link = links[i].split(";"); switch (link[1]) { case 'rel=prev': console.log("Poprzedni obrazek: "+link[0]); break; case 'rel=next': console.log("Następny obrazek: "+link[0]); break; } }
Aby kod był kompletny musimy jeszcze przygotować w HTML miejsce na obrazek, np tworząc pusty element:
<img id="photo"/>
Na koniec
Warto poczytać specyfikację obiektu XMLHttpRequest, choć nie należy ona do najlżejszych lektur. Zwłaszcza różne rodzaje responseType są ciekawe. Oprócz XML możemy odbierać dane binarne w różnych formach jak również dane JSON.
Widzimy, że JavaScript pozwala nam swobodnie operować protokołem HTTP. Tworząc serwisy RESTowe możemy bez problemu korzystać z nich bezpośrednio w przeglądarce.
Fajny przykład.
W function response(e) brakuje nawiasu zamykającego „}”.
i jest undefined, można użyć
for (var i = 0, l = links.length; i<l; i++) {
var link = links[i].split(";");
Dzięki. Poprawione :)