refactor 3-more into separate books
This commit is contained in:
parent
bd1d5e4305
commit
87639b2740
423 changed files with 9 additions and 9 deletions
153
4-ajax/10-ajax-jsonp/article.md
Normal file
153
4-ajax/10-ajax-jsonp/article.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
# Протокол 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. И, тут же, новый запрос...
|
30
4-ajax/10-ajax-jsonp/jsonp.view/index.html
Normal file
30
4-ajax/10-ajax-jsonp/jsonp.view/index.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script src="scriptRequest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function ok(data) {
|
||||
alert("Загружен пользователь " + data.id + ": " + data.name);
|
||||
}
|
||||
|
||||
function fail(url) {
|
||||
alert('Ошибка при запросе ' + url);
|
||||
}
|
||||
|
||||
function go() {
|
||||
// ответы могут приходить в любой последовательности!
|
||||
scriptRequest("user?id=123", ok, fail); // Загружен
|
||||
scriptRequest("/badurl.js", ok, fail); // fail, 404
|
||||
scriptRequest("index.html", ok, fail); // fail, 200 но некорректный скрипт
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick='go()'>Сделать запросы</button>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
49
4-ajax/10-ajax-jsonp/jsonp.view/scriptRequest.js
Normal file
49
4-ajax/10-ajax-jsonp/jsonp.view/scriptRequest.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
var CallbackRegistry = {}; // реестр
|
||||
|
||||
// при успехе вызовет onSuccess, при ошибке onError
|
||||
function scriptRequest(url, onSuccess, onError) {
|
||||
|
||||
var scriptOk = false; // флаг, что вызов прошел успешно
|
||||
|
||||
// сгенерировать имя JSONP-функции для запроса
|
||||
var callbackName = 'cb' + String(Math.random()).slice(-6);
|
||||
|
||||
// укажем это имя в URL запроса
|
||||
url += ~url.indexOf('?') ? '&' : '?';
|
||||
url += 'callback=CallbackRegistry.'+callbackName;
|
||||
|
||||
// ..и создадим саму функцию в реестре
|
||||
CallbackRegistry[callbackName] = function(data) {
|
||||
scriptOk = true; // обработчик вызвался, указать что всё ок
|
||||
delete CallbackRegistry[callbackName]; // можно очистить реестр
|
||||
onSuccess(data); // и вызвать onSuccess
|
||||
};
|
||||
|
||||
// эта функция сработает при любом результате запроса
|
||||
// важно: при успешном результате - всегда после JSONP-обработчика
|
||||
function checkCallback() {
|
||||
if (scriptOk) return; // сработал обработчик?
|
||||
delete CallbackRegistry[callbackName];
|
||||
onError(url); // нет - вызвать onError
|
||||
}
|
||||
|
||||
var script = document.createElement('script');
|
||||
|
||||
// в старых IE поддерживается только событие, а не onload/onerror
|
||||
// в теории 'readyState=loaded' означает "скрипт загрузился",
|
||||
// а 'readyState=complete' -- "скрипт выполнился", но иногда
|
||||
// почему-то случается только одно из них, поэтому проверяем оба
|
||||
script.onreadystatechange = function() {
|
||||
if (this.readyState == 'complete' || this.readyState == 'loaded'){
|
||||
this.onreadystatechange = null;
|
||||
setTimeout(checkCallback, 0); // Вызвать checkCallback - после скрипта
|
||||
}
|
||||
}
|
||||
|
||||
// события script.onload/onerror срабатывают всегда после выполнения скрипта
|
||||
script.onload = script.onerror = checkCallback;
|
||||
script.src = url;
|
||||
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
38
4-ajax/10-ajax-jsonp/jsonp.view/server.js
Normal file
38
4-ajax/10-ajax-jsonp/jsonp.view/server.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
var http = require('http');
|
||||
var url = require('url');
|
||||
var static = require('node-static');
|
||||
var file = new static.Server('.', { cache: 0 });
|
||||
|
||||
|
||||
function accept(req, res) {
|
||||
|
||||
var urlParsed = url.parse(req.url, true);
|
||||
|
||||
if (urlParsed.pathname == '/user') {
|
||||
var id = urlParsed.query.id;
|
||||
var callback = urlParsed.query.callback;
|
||||
|
||||
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
||||
|
||||
var user = {
|
||||
name: "Вася",
|
||||
id: id
|
||||
};
|
||||
|
||||
res.end(callback + '(' + JSON.stringify(user) + ')');
|
||||
|
||||
} else {
|
||||
file.serve(req, res);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ------ запустить сервер -------
|
||||
|
||||
if (!module.parent) {
|
||||
http.createServer(accept).listen(8080);
|
||||
} else {
|
||||
exports.accept = accept;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue