JSONP – sposób na obejście Same-origin policy
Nazwa JSONP pochodzi od JSON with Padding. Jest to rozwiązanie pozwalające obejść zabezpieczenie Same-origin policy (SOP). Opiera się ono na wyjątku, w którym zewnętrze zasoby możemy załadować przez znacznik <script/>.
Choć to rozwiązanie nie jest już potrzebne dzięki CORS to czasami starsze rozwiązania nie mają ustawionych odpowiednich nagłówków (niezbędnych przy Cross-Origin Resource Sharing), a nie mając dostępu do tego API musimy w inny sposób sobie poradzić. Tutaj przychodzi JSONP, które jest zaimplementowane również w jQuery.
Proste API w PHP
Wyobraźmy sobie, że pod adresem http://www.bogolubow.com/api/datetime.php znajduje się API, które przekazuje aktualną datę i czas. Po wpisaniu tego URL do paska adresu przeglądarki moglibyśmy zobaczyć poniższy tekst:
{"date": "2017-04-12", "time": "10:24:04"}
Aby osiągnąć taki efekt musielibyśmy utworzyć plik datetime.php z poniższym kodem:
<?php $json = '{"date": "'. date('Y-m-d') .'", "time": "'. date('H:i:s') .'"}'; echo $json;
W linii 3 zapisuje dane jako string do zmiennej $json. Ostatnia tj. 4 linia wyświetla ciąg znaków zawarty w zmiennej $json.
Implementacja JSONP
Teraz wystarczy dodanie znacznika <script/> do naszej strony np. http://www.webperfection.pl/jsonp.html:
<script src="http://www.bogolubow.com/api/datetime.php"></script>
Co prawda mamy błąd składni (to nie prawidłowy kod JavaScript) to dane odebraliśmy z zewnętrznego zasobu. Teraz jeśli odpowiednio przygotujemy dane zwracane przez datetime.php to być może w konsoli nie wyświetli się błąd.
Zmieńmy kod PHP w poniższy sposób:
<?php $json = '{"date": "'. date('Y-m-d') .'", "time": "'. date('H:i:s') .'"}'; echo 'jsonp('.$json.')';
To spowoduje, że wysyłane dane będą wyglądać w taki sposób:
jsonp({"date": "2017-04-12", "time": "23:30:16"})
Jeśli zajrzysz do konsoli plik jsonp.html wyrzuci błąd tego typu: Uncaught ReferenceError: jsonp is not defined.
Co się stanie jeśli zdefiniujemy jsonp? Zauważmy, że jsonp zawiera wywołanie więc spróbujmy zdefiniować tą funkcję wraz z jednym parametrem. Teraz mamy już dwa skrypty podpięte pod jsonp.html:
<script> function jsonp(param) { console.log(param); } </script> <script src="http://www.bogolubow.com/api/datetime.php"></script>
Teraz zamiast błędu w konsoli mamy obiekt. Możemy wykorzystać dane przesłane z zewnętrznego zasobu.
Moglibyśmy bardziej skomplikować nasz kod w pliku datetime.php, gdzie dodatkowo moglibyśmy przesyłać jak ma się nazywać nasza funkcja za pomocą metody GET, np.:
<?php $json = '{"date": "'. date('Y-m-d') .'", "time": "'. date('H:i:s') .'"}'; echo $_GET['callback'].'('.$json.')';
W ostatniej linii odbieramy dane przesłane metodą GET o odpowiednim kluczu.
Teraz nasz kod w pliku jsonp.html mógłby wyglądać w taki sposób:
<script> function fn(param) { console.log(param.date, param.time); } </script> <script src="http://www.bogolubow.com/api/datetime.php?callback=fn"></script>
Nasz kod jest bardziej elastyczny. Definiujemy jak ma się nazywać funkcja, która będzie odbierać dane z zewnętrznego zasobu.
JSONP w jQuery
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script> $.ajax({ url: 'http://www.bogolubow.com/api/datetime.php', dataType: 'jsonp', }).done(function(res){ console.log(res) }).fail(function(error){ console.log(error); }) </script>
jQuery automatycznie tworzy element <script/> i definiuje atrybut src. Do adresu dodaje parametr GET o kluczu callback, dzięki czemu wiadomo jaka funkcja odbierze dane. W moim przypadku adres do API wyglądał w taki sposób:
datetime.php?callback=jQuery321021464946746158908_1492034568112&_=1492034568113
Natomiast w konsoli pojawił się obiekt, który został zwrócony przez API.