153 lines
7.3 KiB
Markdown
153 lines
7.3 KiB
Markdown
# Протокол JSONP
|
||
|
||
Если создать тег `<script src>`, то при добавлении в документ запустится процесс загрузки `src`. В ответ сервер может прислать скрипт, содержащий нужные данные.
|
||
|
||
Таким образом можно запрашивать данные с любого сервера, в любом браузере, без каких-либо разрешений и дополнительных проверок.
|
||
|
||
Протокол JSONP -- это "надстройка" над таким способом коммуникации. Здесь мы рассмотрим его использование в деталях.
|
||
|
||
[cut]
|
||
|
||
## Запрос
|
||
|
||
Простейший пример запроса:
|
||
|
||
```js
|
||
function addScript(src) {
|
||
var elem = document.createElement("script");
|
||
elem.src = src;
|
||
document.head.appendChild(elem);
|
||
}
|
||
|
||
addScript('user?id=123');
|
||
```
|
||
|
||
Такой вызов добавит в `HEAD` документа тег:
|
||
|
||
```js
|
||
<script src="/user?id=123"></script>
|
||
```
|
||
|
||
Браузер тут же обработает его: запросит `/user?id=123` с заданного URL и выполнит.
|
||
|
||
## Обработка ответа, JSONP
|
||
|
||
В примере выше рассмотрено создание запроса, но как получить ответ? Допустим, сервер хочет прислать объект с данными.
|
||
|
||
Конечно, он может присвоить её в переменную, например так:
|
||
|
||
|
||
```js
|
||
// ответ сервера
|
||
var user = {name: "Вася", age: 25 };
|
||
```
|
||
|
||
...А браузер по `script.onload` отловит окончание загрузки и прочитает значение `user`.
|
||
|
||
Но что, если одновременно делается несколько запросов? Получается, нужно присваивать в разные переменные.
|
||
|
||
Протокол JSONP как раз и призван облегчить эту задачу.
|
||
|
||
Он очень простой:
|
||
|
||
<ol>
|
||
<li>Вместе с запросом клиент в специальном, заранее оговорённом, параметре передаёт название функции.
|
||
|
||
Обычно такой параметр называется `callback`. Например :
|
||
|
||
```js
|
||
addScript('user?id=123&*!*callback=onUserData*/!*');
|
||
```
|
||
</li>
|
||
<li>Cервер кодирует данные в JSON и оборачивает их в функцию, название которой получает из параметра `callback`:
|
||
|
||
```js
|
||
// ответ сервера
|
||
onUserData({
|
||
name: "Вася",
|
||
age: 25
|
||
});
|
||
```
|
||
</li>
|
||
</ol>
|
||
|
||
Это и называется JSONP ("JSON with Padding").
|
||
|
||
|
||
[warn header="Аспект безопасности"]
|
||
Клиентский код должен доверять серверу при таком запросе. Ведь серверу ничего не стоит добавить в скрипт любые команды.
|
||
[/warn]
|
||
|
||
## Реестр CallbackRegistry
|
||
|
||
В примере выше функция `onUserData` должна быть глобальной, ведь `<script src>` выполняется в глобальной области видимости.
|
||
|
||
Хотелось бы не загрязнять глобальное пространство имён, или по крайней мере свести загрязнение к минимуму.
|
||
|
||
Как правило, для этого создают один глобальный объект "реестр", который мы назовём `CallbackRegistry`. Далее для каждого запроса в нём генерируется временная функция.
|
||
|
||
Тег будет выглядеть так:
|
||
|
||
```html
|
||
<script src="user?id=123&callback=*!*CallbackRegistry.func12345*/!*"></script>
|
||
```
|
||
|
||
Сервер обернёт ответ в функцию `CallbackRegistry.func12345`, она вызывает нужный обработчик и очищает память, удаляя себя.
|
||
|
||
Далее мы посмотрим более полный код всего этого, но перед этим -- важный момент! Нужно предусмотреть обработку ошибок.
|
||
|
||
## Обнаружение ошибок
|
||
|
||
При запросе данных при помощи `SCRIPT` возможны различные ошибки:
|
||
|
||
<ol>
|
||
<li>Скрипт может не загрузиться: отказ в соединении, разрыв связи...</li>
|
||
<li>Ошибка HTTP, например 500.</li>
|
||
<li>Скрипт загрузился, но внутри некорректен и не вызывает функцию. Например, на сервере произошла ошибка и в ответе передан её текст, а вовсе не данные.</li>
|
||
</ol>
|
||
|
||
Чтобы отловить их все "одним махом", используем следующий алгоритм:
|
||
|
||
<ol>
|
||
<li>Создаётся `<script>`.</li>
|
||
<li>На `<script>` ставятся обработчики `onreadystatechange` (для старых IE) и `onload/onerror` (для остальных браузеров).</li>
|
||
<li>При загрузке скрипт выполняет функцию-коллбэк `CallbackRegistry...`. Пусть она при запуске ставит флажок "все ок". А мы в обработчиках проверим -- если флага нет, то функция не вызывалась -- стало быть, ошибка при загрузке или содержимое скрипта некорректно.</li>
|
||
</ol>
|
||
|
||
## Полный пример
|
||
|
||
Итак, код функции, которая вызывается с `url` и коллбэками.
|
||
|
||
Он совсем небольшой, а без комментариев был бы ещё меньше:
|
||
|
||
```js
|
||
//+ src="jsonp/scriptRequest.js"
|
||
```
|
||
|
||
Пример использования:
|
||
```js
|
||
function ok(data) {
|
||
alert("Загружен пользователь " + data.name);
|
||
}
|
||
|
||
function fail(url) {
|
||
alert('Ошибка при запросе ' + url);
|
||
}
|
||
|
||
// Внимание! Ответы могут приходить в любой последовательности!
|
||
scriptRequest("user?id=123", ok, fail); // Загружен
|
||
scriptRequest("/badurl.js", ok, fail); // fail, 404
|
||
scriptRequest("/", ok, fail); // fail, 200 но некорректный скрипт
|
||
```
|
||
|
||
Демо, по нажатию на кнопке запускаются запросы выше:
|
||
|
||
[codetabs src="jsonp" height=100]
|
||
|
||
|
||
## COMET
|
||
|
||
|
||
COMET через `SCRIPT` реализуется при помощи длинных опросов, также как мы обсуждали в главе [](/xhr-longpoll).
|
||
|
||
То есть, создаётся тег `<script>`, браузер запрашивает скрипт у сервера и... Сервер оставляет соединение висеть, пока не появится, что сказать. Когда сервер хочет отправить сообщение -- он отвечает, используя формат JSONP. И, тут же, новый запрос...
|