renovations
This commit is contained in:
parent
d1b484913a
commit
6414382e2e
5 changed files with 277 additions and 119 deletions
|
@ -14,8 +14,8 @@ Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
|
|||
Connection:keep-alive
|
||||
Host:google.ru
|
||||
*!*
|
||||
Origin:http://learn.javascript.ru
|
||||
Referer:http://learn.javascript.ru/node/add/learning-task
|
||||
Origin:http://javascript.ru
|
||||
Referer:http://javascript.ru/some/url
|
||||
*/!*
|
||||
```
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
|
|||
var xhr = new XHR();
|
||||
|
||||
// (2) запрос на другой домен :)
|
||||
xhr.open('GET', 'http://anywhere.com/path', true);
|
||||
xhr.open('GET', 'http://anywhere.com/request', true);
|
||||
|
||||
xhr.onload = function() {
|
||||
alert(this.responseText);
|
||||
|
@ -42,29 +42,76 @@ xhr.send();
|
|||
|
||||
## Контроль безопасности
|
||||
|
||||
Кросс-доменные запросы проходят специальный контроль безопасности, цель которого -- не дать злым хакерам(tm) завоевать интернет.
|
||||
|
||||
Серьёзно. Разработчики стандарта предусмотрели все заслоны, чтобы "злой хакер" не смог, воспользовавшись новым стандартом, сделать что-то принципиально отличное от того, что и так мог раньше и, таким образом, "сломать" не ожидающий этого старый сервер.
|
||||
|
||||
Давайте, на минуточку, вообразим, что появился стандарт, который даёт, без ограничений, возможность делать любой странице HTTP-запросы куда угодно, какие угодно.
|
||||
|
||||
Как сможет этим воспользоваться злой хакер?
|
||||
|
||||
Он сделает свой сайт, например `http://evilhacker.com` и заманит туда посетителя (а может посетитель попадёт на "злонамеренную" страницу и по ошибке -- не так важно).
|
||||
|
||||
Когда посетитель зайдёт на `http://evilhacker.com`, он автоматически запустит JS-скрипт на странице. Этот скрипт сможет бы сделать HTTP-запрос на почтовый сервер, к примеру, `http://gmail.com`. А ведь обычно HTTP-запросы идут с куками посетителя и другими авторизующими заголовками.
|
||||
|
||||
Поэтому хакер смог бы написать на `http://evilhacker.com` код, который, сделав GET-запрос на `http://gmail.com`, получит информацию из почтового ящика посетителя. Проанализирует её, сделает ещё пачку POST-запросов для отправку писем от имени посетителя. Затем настанет очередь онлайн-банка и так далее.
|
||||
|
||||
Спецификация [CORS](http://www.w3.org/TR/cors/) налагает специальные ограничения на запросы, которые призваны не допустить подобного апокалипсиса.
|
||||
|
||||
Запросы в ней делятся на два вида.
|
||||
|
||||
[Простыми](http://www.w3.org/TR/cors/#terminology) считаются запросы, если они удовлетворяют следующим двум условиям:
|
||||
|
||||
<ol>
|
||||
<li>[Простой метод](http://www.w3.org/TR/cors/#simple-method): GET, POST или HEAD</li>
|
||||
<li>[Простые заголовки](http://www.w3.org/TR/cors/#simple-header) -- только из списка:
|
||||
<ul>
|
||||
<li>`Accept`</li>
|
||||
<li>`Accept-Language`</li>
|
||||
<li>`Content-Language`</li>
|
||||
<li>`Content-Type` со значением `application/x-www-form-urlencoded`, `multipart/form-data` или `text/plain`.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
"Непростыми" считаются все остальные, например, запрос с методом `PUT` или с заголовком `Authorization` будет не подходит под ограничения выше.
|
||||
|
||||
Принципиальная разница между ними заключается в том, что "простой" запрос можно сформировать и отправить на сервер и без XMLHttpRequest, например при помощи HTML-формы.
|
||||
|
||||
То есть, злой хакер на странице `http://evilhacker.com` и до появления CORS мог отправить произвольный GET-запрос куда угодно. Например, если создать и добавить в документ элемент `<script src="любой url">`, то браузер сделает GET-запрос на этот URL.
|
||||
|
||||
Аналогично, злой хакер и ранее мог на своей странице объявить и, при помощи JavaScript, отправить HTML-форму с методом GET/POST и кодировкой `multipart/form-data`. А значит, даже старый сервер наверняка предусматривает возможность таких атак и умеет от них защищаться.
|
||||
|
||||
А вот запросы с нестандартными заголовками или с методом `DELETE` таким образом не создать. Поэтому старый сервер может быть к ним не готов. Или, к примеру, он может полагать, что такие запросы веб-страница в принципе не умеет присылать, значит они пришли из привелигированного приложения, и дать им слишком много прав.
|
||||
|
||||
Поэтому при посылкой "непростых" запросов нужно специальным образом спросить у сервера, согласен ли он в принципе на подобные кросс-доменные запросы или нет? И, если сервер не ответит, что согласен -- значит, нет.
|
||||
|
||||
[summary]
|
||||
В спецификации CORS, как мы увидим далее, есть много деталей, но все они объединены единым принципом: новые возможности доступны только с явного согласия сервера (по умолчанию -- нет).
|
||||
[/summary]
|
||||
|
||||
## CORS для простых запросов
|
||||
|
||||
В кросс-доменный запрос браузер автоматически добавляет заголовок `Origin`, содержащий домен, с которого осуществлён запрос.
|
||||
|
||||
В данном случае заголовки будут примерно такие:
|
||||
В случае запроса на `http://anywhere.com/request` с `http://javascript.ru/page` заголовки будут примерно такие:
|
||||
|
||||
```
|
||||
GET /vote.php
|
||||
Accept:*/*
|
||||
Accept-Charset:windows-1251,utf-8;q=0.7,*;q=0.3
|
||||
Accept-Encoding:gzip,deflate,sdch
|
||||
Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
|
||||
Connection:keep-alive
|
||||
GET /request
|
||||
Host:anywhere.com
|
||||
*!*
|
||||
Origin:http://javascript.ru
|
||||
*/!*
|
||||
...
|
||||
```
|
||||
|
||||
Сервер должен, со своей стороны, ответить специальными заголовками, разрешает ли он такой запрос к себе.
|
||||
|
||||
Если сервер разрешает кросс-доменный запрос с этого домена -- он должен добавить к ответу заголовок `Access-Control-Allow-Origin`, содержащий домен запроса (в данном случае "javascript.ru") или звёздочку `*`.
|
||||
|
||||
**Если сервер разрешает кросс-доменный запрос с этого домена -- он должен добавить к ответу заголовок `Access-Control-Allow-Origin`, содержащий домен запроса (в данном случае "javascript.ru") или звёздочку `*`.**
|
||||
**Только при наличии такого заголовка в ответе -- браузер сочтёт запрос успешным, а иначе JavaScript получит ошибку.**
|
||||
|
||||
Только при наличии такого заголовка в ответе -- браузер сочтёт запрос успешным, а иначе JavaScript получит ошибку.
|
||||
<img src="xhr-another-domain.svg">
|
||||
|
||||
То есть, ответ сервера может быть примерно таким:
|
||||
|
||||
|
@ -76,27 +123,19 @@ Access-Control-Allow-Origin: http://javascript.ru
|
|||
*/!*
|
||||
```
|
||||
|
||||
**Описанный выше поток выполнения обладает двумя особенностями:**
|
||||
Если `Access-Control-Allow-Origin` нет, то браузер считает, что разрешение не получено, и завершает запрос с ошибкой.
|
||||
|
||||
<ol>
|
||||
<li>Не передаются куки и заголовки HTTP-авторизации. Параметры `user` и `password` в методе `open` игнорируются. </li>
|
||||
<li>Речь пока только о GET и POST-запросах, другие методы обрабатываются по-другому.</li>
|
||||
</ol>
|
||||
При таких запросах не передаются куки и заголовки HTTP-авторизации. Параметры `user` и `password` в методе `open` игнорируются. Мы рассмотрим, как разрешить их передачу, чуть далее.
|
||||
|
||||
[warn header="Что может сделать хакер, используя такие запросы?"]
|
||||
Описанные выше ограничения приводят к тому, что запрос полностью безопасен.
|
||||
|
||||
Действительно, нехороший человек со злой страницы может сформировать любой GET/POST-запрос и отправить его, но без разрешения сервера ответа он не получит.
|
||||
Действительно, злая страница может сформировать любой GET/POST-запрос и отправить его, но без разрешения сервера ответа она не получит.
|
||||
|
||||
А без ответа такой запрос, по сути, эквивалентен отправке формы GET/POST, причём без авторизации.
|
||||
А без ответа такой запрос, по сути, эквивалентен отправке формы GET/POST, причём без авторизации.
|
||||
[/warn]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Ограничения IE9-
|
||||
## Ограничения IE9-
|
||||
|
||||
В IE9- используется `XDomainRequest`, который представляет собой урезанный `XMLHttpRequest`.
|
||||
|
||||
|
@ -116,13 +155,12 @@ Access-Control-Allow-Origin: http://javascript.ru
|
|||
|
||||
Современный стандарт [XMLHttpRequest](http://www.w3.org/TR/XMLHttpRequest/) предусматривает средства для преодоления этих ограничений, но на момент выхода IE8 они ещё не были проработаны, поэтому их не реализовали. А IE9 исправил некоторые ошибки, но в общем не добавил ничего нового.
|
||||
|
||||
Так как большинство сайтов хотят поддерживать IE9-, то на практике кросс-доменные запросы редко используют, предпочитая другие способы кросс-доменной коммуникации. Например, динамически создаваемый тег `SCRIPT` или вспомогательный `IFRAME` с другого домена. Мы разберём эти подходы в последующих главах.
|
||||
Поэтому на сайтах, которые хотят поддерживать IE9-, то на практике кросс-доменные запросы редко используют, предпочитая другие способы кросс-доменной коммуникации. Например, динамически создаваемый тег `SCRIPT` или вспомогательный `IFRAME` с другого домена. Мы разберём эти подходы в последующих главах.
|
||||
|
||||
[smart header="Как разрешить кросс-доменные запросы от доверенного сайта в IE9-?"]
|
||||
Разрешить кросс-доменные запросы для "доверенных" сайтов можно в настройках IE, во вкладке "Безопасность", включив пункт "Доступ к источникам данных за пределами домена".
|
||||
|
||||
Разрешить кросс-доменные запросы можно в настройках IE, во вкладке "Безопасность", включив пункт "Доступ к источникам данных за пределами домена".
|
||||
|
||||
Обычно это делается не для всех сайтов, а для зоны "Надёжные узлы", после чего в неё вносится доверенный сайт. Теперь он может делать кросс-доменные запросы `XMLHttpRequest`.
|
||||
Обычно это делается для зоны "Надёжные узлы", после чего в неё вносится доверенный сайт. Теперь он может делать кросс-доменные запросы `XMLHttpRequest`.
|
||||
|
||||
Этот способ можно применить для корпоративных сайтов, а также в тех случаях, когда посетитель заведомо вам доверяет, но почему-то (компьютер на работе, админ запрещает ставить другой браузер?) хочет использовать именно IE. Например, он может предлагаться в качестве дополнительной инструкции "как заставить этот сервис работать под IE".
|
||||
[/smart]
|
||||
|
@ -132,64 +170,68 @@ Access-Control-Allow-Origin: http://javascript.ru
|
|||
|
||||
То есть, можно сделать запрос с `http://javascript.ru` на `http://javascript.ru:8080`, и в IE он не будет считаться кросс-доменным.
|
||||
|
||||
Это позволяет решить некоторые задачи, связанные с взаимодействием различных сервисов в рамках одного сайта.
|
||||
|
||||
Это позволяет решить некоторые задачи, связанные с взаимодействием различных сервисов в рамках одного сайта. Но только для IE.
|
||||
[/smart]
|
||||
|
||||
Есть и другие способы обойти кросс-доменные ограничения, например использование тега `SCRIPT` или `IFRAME`. Их детали мы рассмотрим в следующих главах, так как здесь мы рассматриваем именно `XMLHttpRequest`.
|
||||
Расширенные возможности, описанные далее, поддерживаются всеми современными браузерами, кроме IE9-.
|
||||
|
||||
|
||||
**Расширенные возможности, описанные далее, поддерживаются всеми современными браузерами, кроме IE9-.**
|
||||
## Заголовки ответа
|
||||
|
||||
Чтобы JavaScript мог прочитать HTTP-заголовок ответа, сервер должен указать его имя в `Access-Control-Expose-Headers`.
|
||||
|
||||
Например:
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type:text/html; charset=UTF-8
|
||||
Access-Control-Allow-Origin: http://javascript.ru
|
||||
*!*
|
||||
X-Uid: 123
|
||||
X-Authorization: 2c9de507f2c54aa1
|
||||
Access-Control-Expose-Headers: X-Uid, X-Authentication
|
||||
*/!*
|
||||
```
|
||||
|
||||
По умолчанию скрипт может прочитать из ответа только "простые" заголовки:
|
||||
|
||||
```
|
||||
Cache-Control
|
||||
Content-Language
|
||||
Content-Type
|
||||
Expires
|
||||
Last-Modified
|
||||
Pragma
|
||||
```
|
||||
|
||||
...То есть, `Content-Type` получить всегда можно, а доступ к специфическим заголовкам нужно открывать явно.
|
||||
|
||||
|
||||
## Запросы от имени пользователя
|
||||
|
||||
Чтобы браузер передал вместе с запросом куки и, при необходимости, HTTP-авторизацию, поставьте запросу `xhr.withCredentials = true`:
|
||||
По умолчанию браузер не передаёт с запросом куки и авторизующие заголовки.
|
||||
|
||||
Чтобы браузер передал вместе с запросом куки и HTTP-авторизацию, нужно поставить запросу `xhr.withCredentials = true`:
|
||||
|
||||
```js
|
||||
var xhr = new XMLHttpRequest(); // XDomainRequest не поддерживает это
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.open('POST', 'http://anywhere.com/request', true)
|
||||
...
|
||||
```
|
||||
|
||||
Далее -- всё как обычно, дополнительных действий со стороны клиента не требуется.
|
||||
|
||||
### Защита от CSRF
|
||||
|
||||
Кросс-доменный `XMLHttpRequest` с куками, безусловно, опаснее, чем анонимный.
|
||||
|
||||
Представьте себе -- человек нечаянно попадает на страницу злого хакера. А она, используя JavaScript, отправляет кросс-доменный запрос на почтовый сервер, например GMail.
|
||||
|
||||
Если с запросом отправятся куки, то получится, что этот запрос выполнился "от имени посетителя".
|
||||
|
||||
Подобные атаки давно существуют в интернете и называются [CSRF-атака](http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D0%BB%D0%BA%D0%B0_%D0%BC%D0%B5%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D1%8B%D1%85_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2). Обычно они выполняются при помощи динамической генерации о отправки POST-формы. "Злая" страница может создать произвольную форму и отправить её на почтовый сервер. При этом с формой передадутся куки, то есть авторизация посетителя. Если почтовый сервер полагается только на них, то он выполнит запрошенное действие от имени пользователя.
|
||||
|
||||
**Чтобы защититься от CSRF-атак, почтовый сервер при генерации формы отправки сообщения добавляет в неё специальный ключ:**
|
||||
|
||||
```html
|
||||
<form action="send.php" method="POST">
|
||||
*!*
|
||||
<input type="hidden" name="csrf-token" value="abcdefabcdef">
|
||||
*/!*
|
||||
...
|
||||
</form>
|
||||
```
|
||||
|
||||
Этот ключ генерируется случайным образом и привязывается к сессии посетителя. Если посетитель честно открыл браузер и отправил форму со страницы, то в ней будет этот ключ. А "злая" страница этот ключ не знает, ведь его сгенерировал сервер. Даже открыв страницу с почтового сервера в `IFRAME`, она не сможет получить информацию со страницы -- `IFRAME`-то на другом домене.
|
||||
|
||||
...А без ключа она не сможет отправить сообщение. Сервер ведь его проверяет. Правильный ключ доказывает, что форма отправлена именно со страницы почтового сервера.
|
||||
|
||||
В случае с кросс-доменным `XMLHttpRequest` ситуация принципиально меняется и атака становится куда опаснее!
|
||||
|
||||
Если сервер разрешает делать к себе `XMLHttpRequest` с `withCredentials`, то эту защиту легко обойти! Ведь `XMLHttpRequest` не просто делает запрос, а возвращает ответ, к которому JavaScript, (в отличие от результата отправки формы на другой домен) имеет полный доступ.
|
||||
|
||||
Например, "злая" страница может запросить страницу с формой отправки, найти в `responseText` нужный `<input name="csrf-token">` -- и вуаля, ключ есть, можно делать новый запрос, уже с гарантией прохождения.
|
||||
Такой `XMLHttpRequest` с куками, естественно, требует от сервера больше разрешений, чем "анонимный".
|
||||
|
||||
**Поэтому для запросов с `withCredentials` предусмотрено дополнительное подтверждение со стороны сервера.**
|
||||
|
||||
Он должен показать, что разрешает делать не просто запросы, а запросы от имени посетителя. Как мы видим, это очень серьёзное разрешение.
|
||||
|
||||
**При запросе с `withCredentials` сервер должен вернуть уже не один, а два заголовка: `Access-Control-Allow-Origin: домен` и `Access-Control-Allow-Credentials: true`.**
|
||||
При запросе с `withCredentials` сервер должен вернуть уже не один, а два заголовка:
|
||||
<ul>
|
||||
<li>`Access-Control-Allow-Origin: домен`</li>
|
||||
<li>`Access-Control-Allow-Credentials: true`</li>
|
||||
</ul>
|
||||
|
||||
Пример заголовков:
|
||||
|
||||
|
@ -204,61 +246,41 @@ Access-Control-Allow-Credentials: true
|
|||
|
||||
Использование звёздочки `*` в `Access-Control-Allow-Origin` при этом запрещено.
|
||||
|
||||
Если заголовков не будет, то браузер не даст JavaScript'у доступ к ответу сервера. И получится, что запрос -- не опаснее обычной CSRF-атаки.
|
||||
Если этих заголовков не будет, то браузер не даст JavaScript'у доступ к ответу сервера.
|
||||
|
||||
## Чтение заголовков
|
||||
|
||||
Чтобы клиент мог прочитать HTTP-заголовок, сервер должен указать его имя в `Access-Control-Expose-Headers`.
|
||||
|
||||
Например:
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type:text/html; charset=UTF-8
|
||||
*!*
|
||||
X-Uid: 123
|
||||
X-Authorization: abcdefabcdef
|
||||
Access-Control-Allow-Origin: http://javascript.ru
|
||||
Access-Control-Expose-Headers: X-Uid, X-Authentication
|
||||
*/!*
|
||||
```
|
||||
|
||||
Это ограничение относится ко всем заголовкам, кроме "простых":
|
||||
|
||||
```
|
||||
Cache-Control
|
||||
Content-Language
|
||||
Content-Type
|
||||
Expires
|
||||
Last-Modified
|
||||
Pragma
|
||||
```
|
||||
|
||||
**...То есть, `Content-Type` получить всегда можно, а доступ к специфическим заголовкам нужно открывать явно.**
|
||||
|
||||
## Нестандартные методы и заголовки
|
||||
## "Непростые" запросы
|
||||
|
||||
В кросс-доменном `XMLHttpRequest` можно указать не только `GET/POST`, но и любой другой метод, например `PUT`, `DELETE`.
|
||||
|
||||
Когда-то никто и не думал, что страница сможет сделать такие запросы. Поэтому ряд веб-сервисов написаны в предположении, что "если метод -- нестандартный, то это не браузер". Некоторые веб-сервисы даже учитывают это при проверке прав доступа.
|
||||
|
||||
**Чтобы пресечь любые недопонимания, браузер использует предзапрос в случаях:**
|
||||
Чтобы пресечь любые недопонимания, браузер использует предзапрос в случаях, когда:
|
||||
|
||||
<ul>
|
||||
<li>Если метод -- не GET и не POST</li>
|
||||
<li>Если метод -- POST, но заголовок `Content-Type` имеет значение отличное от `application/x-www-form-urlencoded`, `multipart/form-data` или `text/plain`, например `application/xml`.</li>
|
||||
<li>Если устанавливаются другие HTTP-заголовки</li>
|
||||
<li>Если метод -- не GET / POST / HEAD.</li>
|
||||
<li>Если заголовок `Content-Type` имеет значение отличное от `application/x-www-form-urlencoded`, `multipart/form-data` или `text/plain`, например `application/xml`.</li>
|
||||
<li>Если устанавливаются другие HTTP-заголовки, кроме `Accept`, `Accept-Language`, `Content-Language`.</li>
|
||||
</ul>
|
||||
|
||||
...Любое из условий выше ведёт к тому, что браузер cделает два HTTP-запроса.
|
||||
|
||||
Первый запрос называется "предзапрос" (английский термин "preflight"), он использует метод `OPTIONS`. Его задача -- спросить сервер, разрешает ли он использовать выбранный метод и заголовки.
|
||||
Первый запрос называется "предзапрос" (английский термин "preflight"). Браузер делает его целиком по своей инициативе, из JavaScript мы о нём ничего не знаем, хотя можем увидеть в инструментах разработчика.
|
||||
|
||||
Название метода браузер передаёт в `Access-Control-Request-Method`, а если в `XMLHttpRequest` добавлены особые заголовки, то и их тоже -- в `Access-Control-Request-Headers`.
|
||||
Этот запрос использует метод `OPTIONS`. Он не содержит тела и содержит название желаемого метода в заголовке `Access-Control-Request-Method`, а если добавлены особые заголовки, то и их тоже -- в `Access-Control-Request-Headers`.
|
||||
|
||||
Его задача -- спросить сервер, разрешает ли он использовать выбранный метод и заголовки.
|
||||
|
||||
На этот запрос сервер должен ответить статусом 200, указав заголовки `Access-Control-Allow-Method: метод` и, если есть заголовки, `Access-Control-Allow-Headers: разрешённые заголовки`.
|
||||
|
||||
Дополнительно он может указать `Access-Control-Max-Age: sec`, где `sec` -- количество секунд, на которые нужно закэшировать разрешение. Тогда при последующих вызовах метода браузер уже не будет делать предзапрос.
|
||||
|
||||
<img src="xhr-preflight.svg">
|
||||
|
||||
Давайте рассмотрим предзапрос на конкретном примере.
|
||||
|
||||
### Пример запроса COPY
|
||||
|
||||
Рассмотрим запрос `COPY`, который используется в протоколе[WebDAV](http://www.webdav.org/specs/rfc2518.html) для управления файлами через HTTP:
|
||||
Рассмотрим запрос `COPY`, который используется в протоколе [WebDAV](http://www.webdav.org/specs/rfc2518.html) для управления файлами через HTTP:
|
||||
|
||||
```js
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
@ -269,10 +291,16 @@ xhr.setRequestHeader('Destination', 'http://site.com/~ilya.bak');
|
|||
xhr.onload = ...
|
||||
xhr.onerror = ...
|
||||
|
||||
xhr.send('...');
|
||||
xhr.send();
|
||||
```
|
||||
|
||||
Браузер сначала шлёт предзапрос `OPTIONS`:
|
||||
Этот запрос "непростой" по двум причинам (достаточно было бы одной из них):
|
||||
<ol>
|
||||
<li>Метод `COPY`.</li>
|
||||
<li>Заголовок `Destination`.</li>
|
||||
</ol>
|
||||
|
||||
Поэтому браузер, по своей инициативе, шлёт предварительный запрос `OPTIONS`:
|
||||
|
||||
```
|
||||
OPTIONS /~ilya HTTP/1.1
|
||||
|
@ -287,25 +315,37 @@ Access-Control-Request-Headers: Destination
|
|||
*/!*
|
||||
```
|
||||
|
||||
На этот запрос сервер должен ответить статусом 200, указав заголовки `Access-Control-Allow-Method: метод` и, если есть заголовки, `Access-Control-Allow-Headers: разрешённые заголовки`.
|
||||
Обратим внимание на детали:
|
||||
|
||||
Дополнительно он может указать `Access-Control-Max-Age: sec`, где `sec` -- количество секунд, на которые нужно закэшировать разрешение. Тогда при последующих вызовах метода браузер уже не будет делать предзапрос.
|
||||
<ul>
|
||||
<li>Адрес -- тот же, что и у основного запроса: `http://site.com/~ilya`.</li>
|
||||
<li>Стандартные заголовки `Accept`, `Accept-Encoding`, `Connection` присутствуют.</li>
|
||||
<li>Кросс-доменные специальные заголовки:
|
||||
<ul>
|
||||
<li>`Origin` -- домен, с которого сделан запрос.</li>
|
||||
<li>`Access-Control-Request-Method` -- желаемый метод.</li>
|
||||
<li>`Access-Control-Request-Headers` -- желаемый "непростой" заголовок.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
В протоколе WebDav разрешены многие методы и заголовки, которые и перечислим в ответе:
|
||||
На этот запрос сервер должен ответить статусом 200, указав заголовки `Access-Control-Allow-Method: COPY` и `Access-Control-Allow-Headers: Destination`.
|
||||
|
||||
Но в протоколе WebDav разрешены многие методы и заголовки, которые имеет смысл сразу перечислить в ответе:
|
||||
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/plain
|
||||
*!*Access-Control-Allow-Methods:*/!* PROPFIND, PROPPATCH, COPY, MOVE, DELETE, MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, OPTIONS, GET, POST
|
||||
*!*Access-Control-Allow-Headers:*/!* Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control
|
||||
*!*Access-Control-Max-Age:*/!* 86400
|
||||
*!*Access-Control-Allow-Methods*/!*: PROPFIND, PROPPATCH, COPY, MOVE, DELETE, MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, OPTIONS, GET, POST
|
||||
*!*Access-Control-Allow-Headers*/!*: Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control
|
||||
*!*Access-Control-Max-Age*/!*: 86400
|
||||
```
|
||||
|
||||
Браузер видит, что метод `COPY` -- в числе разрешённых и заголовок `Destination` -- тоже, и дальше он шлёт уже основной запрос.
|
||||
|
||||
При этом ответ на предзапрос он закэширует на 86400 сек (ровно сутки), так что следующие вызовы `COPY` сразу отправят основной запрос.
|
||||
При этом ответ на предзапрос он закэширует на 86400 сек (сутки), так что последующие аналогичные вызовы сразу отправят основной запрос, без `OPTIONS`.
|
||||
|
||||
Основной запрос браузера:
|
||||
Основной запрос браузер выполняет уже в "обычном" кросс-доменном режиме:
|
||||
|
||||
```
|
||||
COPY /~ilya HTTP/1.1
|
||||
|
@ -317,7 +357,7 @@ Origin: http://javascript.ru
|
|||
*/!*
|
||||
```
|
||||
|
||||
Ответ сервера, согласно спецификации [WebDav COPY](http://www.webdav.org/specs/rfc2518.html#rfc.section.8.8.8):
|
||||
Ответ сервера, согласно спецификации [WebDav COPY](http://www.webdav.org/specs/rfc2518.html#rfc.section.8.8.8), может быть примерно таким:
|
||||
|
||||
```
|
||||
HTTP/1.1 207 Multi-Status
|
||||
|
@ -333,4 +373,5 @@ Access-Control-Allow-Origin: http://javascript.ru
|
|||
</d:multistatus>
|
||||
```
|
||||
|
||||
Таким образом, через `XMLHttpRequest` можно удобно использовать любые методы и заголовки, главное чтобы сервер это разрешал.
|
||||
Так как `Access-Control-Allow-Origin` содержит правильный домен, то браузер вызовет `xhr.onload` и запрос будет завершён.
|
||||
|
||||
|
|
50
3-more/2-ajax/5-xhr-crossdomain/xhr-another-domain.svg
Normal file
50
3-more/2-ajax/5-xhr-crossdomain/xhr-another-domain.svg
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="610px" height="411px" viewBox="0 0 610 411" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>xhr-another-domain.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="xhr-another-domain.svg" sketch:type="MSArtboardGroup">
|
||||
<rect id="Rectangle-227" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="3" y="15" width="130" height="66"></rect>
|
||||
<text id="JavaScript" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="29" y="50">JavaScript</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-228" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="228" y="15" width="130" height="66"></rect>
|
||||
<text id="Браузер" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="266" y="50">Браузер</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-229" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="454" y="15" width="130" height="66"></rect>
|
||||
<text id="Сервер" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="495" y="50">Сервер</tspan>
|
||||
</text>
|
||||
<path d="M69,81 L69,401" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M294,81 L294,401" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M520,81 L520,401" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M70.5,139.5 L290.5,139.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M279.7,142.5 C283.48,141.45 286.72,140.55 290.5,139.5 C286.72,138.45 283.48,137.55 279.7,136.5" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="send()" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="153" y="132">send()</tspan>
|
||||
</text>
|
||||
<path d="M295.5,189.5 L515.5,189.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M515.5,189.5 C511.72,188.45 508.48,187.55 504.7,186.5 C504.7,188.6 504.7,190.4 504.7,192.5 C508.48,191.45 511.72,190.55 515.5,189.5 C515.5,189.5 515.5,189.5 515.5,189.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="HTTP-запрос" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="370.165039" y="181">HTTP-запрос</tspan>
|
||||
<tspan x="349.822266" y="201" font-size="12">с заголовком Origin</tspan>
|
||||
</text>
|
||||
<path d="M297.5,294.5 L517.5,294.5" id="Line-2" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-2-decoration-1" d="M298.3,294.5 C302.08,293.45 305.32,292.55 309.1,291.5 C309.1,293.6 309.1,295.4 309.1,297.5 C305.32,296.45 302.08,295.55 298.3,294.5 C298.3,294.5 298.3,294.5 298.3,294.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<path d="M71.5,359.5 L291.5,359.5" id="Line-3" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-3-decoration-1" d="M72.3,359.5 C76.08,358.45 79.32,357.55 83.1,356.5 C83.1,358.6 83.1,360.4 83.1,362.5 C79.32,361.45 76.08,360.55 72.3,359.5 C72.3,359.5 72.3,359.5 72.3,359.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="HTTP-ответ" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="371.513672" y="287">HTTP-ответ</tspan>
|
||||
<tspan x="274.748047" y="307" font-size="12">Access-Control-Allow-Origin: * или Origin</tspan>
|
||||
<tspan x="545.251953" y="307"> </tspan>
|
||||
</text>
|
||||
<text id="если-заголовок-стоит" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="80.0869141" y="351">если заголовок стоит onload</tspan>
|
||||
<tspan x="133.967773" y="371">иначе onerror</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
67
3-more/2-ajax/5-xhr-crossdomain/xhr-preflight.svg
Normal file
67
3-more/2-ajax/5-xhr-crossdomain/xhr-preflight.svg
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="610px" height="633px" viewBox="0 0 610 633" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>xhr-preflight.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="xhr-preflight.svg" sketch:type="MSArtboardGroup">
|
||||
<rect id="Rectangle-227" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="3" y="15" width="130" height="66"></rect>
|
||||
<text id="JavaScript" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="29" y="50">JavaScript</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-228" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="228" y="15" width="130" height="66"></rect>
|
||||
<text id="Браузер" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="266" y="50">Браузер</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-229" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="467" y="15" width="130" height="66"></rect>
|
||||
<text id="Сервер" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="503" y="50">Сервер</tspan>
|
||||
</text>
|
||||
<path d="M69,82 L69,602" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M294,82 L294,602" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M533,82 L533,602" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M70.5,139.5 L290.5,139.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M279.7,142.5 C283.48,141.45 286.72,140.55 290.5,139.5 C286.72,138.45 283.48,137.55 279.7,136.5" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="send()" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
|
||||
<tspan x="153" y="132">send()</tspan>
|
||||
</text>
|
||||
<text id="OPTIONS" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="387.55957" y="171">OPTIONS</tspan>
|
||||
<tspan x="394.707031" y="191" font-size="12">Origin</tspan>
|
||||
<tspan x="318.833984" y="211" font-size="12">Access-Control-Request-Method</tspan>
|
||||
<tspan x="315.535156" y="231" font-size="12">Access-Control-Request-Headers</tspan>
|
||||
</text>
|
||||
<path d="M297.5,292.5 L527.5,292.5" id="Line" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M298.3,292.5 C302.08,291.45 305.32,290.55 309.1,289.5 C309.1,291.6 309.1,293.4 309.1,295.5 C305.32,294.45 302.08,293.55 298.3,292.5 C298.3,292.5 298.3,292.5 298.3,292.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="200-OK" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="392.408203" y="285">200 OK</tspan>
|
||||
<tspan x="326.431641" y="305" font-size="12">Access-Control-Allow-Method</tspan>
|
||||
<tspan x="323.132812" y="325" font-size="12">Access-Control-Allow-Headers</tspan>
|
||||
<tspan x="342.925781" y="345" font-size="12">Access-Control-Max-Age</tspan>
|
||||
<tspan x="488.074219" y="345"></tspan>
|
||||
</text>
|
||||
<path d="M298.5,179.5 L528.5,179.5" id="Line-4" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-4-decoration-1" d="M528.5,179.5 C524.72,178.45 521.48,177.55 517.7,176.5 C517.7,178.6 517.7,180.4 517.7,182.5 C521.48,181.45 524.72,180.55 528.5,179.5 C528.5,179.5 528.5,179.5 528.5,179.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<path d="M297.5,501.5 L527.5,501.5" id="Line-2" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-2-decoration-1" d="M298.3,501.5 C302.08,500.45 305.32,499.55 309.1,498.5 C309.1,500.6 309.1,502.4 309.1,504.5 C305.32,503.45 302.08,502.55 298.3,501.5 C298.3,501.5 298.3,501.5 298.3,501.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<path d="M71.5,553.5 L291.5,553.5" id="Line-3" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-3-decoration-1" d="M72.3,553.5 C76.08,552.45 79.32,551.55 83.1,550.5 C83.1,552.6 83.1,554.4 83.1,556.5 C79.32,555.45 76.08,554.55 72.3,553.5 C72.3,553.5 72.3,553.5 72.3,553.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="HTTP-ответ" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="379.513672" y="494">HTTP-ответ</tspan>
|
||||
<tspan x="328.931641" y="514" font-size="12">Access-Control-Allow-Origin</tspan>
|
||||
<tspan x="507.068359" y="514"> </tspan>
|
||||
</text>
|
||||
<text id="если-сервер-разрешил" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="75.2382812" y="545">если сервер разрешил: onload</tspan>
|
||||
<tspan x="132.967773" y="565">иначе onerror</tspan>
|
||||
</text>
|
||||
<path d="M296.5,435.5 L526.5,435.5" id="Line-5" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-5-decoration-1" d="M526.5,435.5 C522.72,434.45 519.48,433.55 515.7,432.5 C515.7,434.6 515.7,436.4 515.7,438.5 C519.48,437.45 522.72,436.55 526.5,435.5 C526.5,435.5 526.5,435.5 526.5,435.5 Z" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
<text id="Основной-HTTP-запрос" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" sketch:alignment="middle" line-spacing="18" fill="#8A704D">
|
||||
<tspan x="339.527344" y="427">Основной HTTP-запрос</tspan>
|
||||
<tspan x="396.707031" y="447" font-size="12">Origin</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
BIN
figures.sketch
BIN
figures.sketch
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue