en.javascript.info/4-ajax/10-ajax-jsonp/article.md
2015-02-27 13:21:58 +03:00

153 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Протокол 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. И, тут же, новый запрос...