renovations

This commit is contained in:
Ilya Kantor 2015-02-23 01:25:31 +03:00
parent d1b484913a
commit 6414382e2e
5 changed files with 277 additions and 119 deletions

View file

@ -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
*/!*
```

View file

@ -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` и запрос будет завершён.

View 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

View 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

Binary file not shown.