Но CORS не рассчитывала на встречу с вами
Сценарий
Изучая исходный код и сетевые запросы определенного популярного блогового сайта (назовем его, э-э… Максимум), вы обнаружили, что у этого сайта действительно есть API, который вы можете вызвать для получения сообщений пользователя (подробнее об этом в последующая статья). Итак, вы решили добавить на свой веб-сайт раздел, в котором вы можете отображать свои последние сообщения, полученные из их API через AJAX, как ваш босс.

Эта проблема
Вы все настраиваете:
let loadPosts = function () {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
let response = JSON.parse(this.responseText);
renderPosts(response); }
}
xhr.open("GET", "https://maximum.blog/@shalvah/posts");
xhr.setRequestHeader("Accept", 'application/json');
xhr.send();
}
И вот что у вас получится:

😭😭😭
Отладка
Если вы не знаете, что здесь происходит, вот краткий обзор. Это результат так называемой политики одного происхождения. Это означает, что запрос AJAX, сделанный из одного домена (например, google.com), может получить доступ только к другим конечным точкам, размещенным на google.com. Вот отрывок из MDN:
По соображениям безопасности браузеры ограничивают HTTP-запросы из разных источников, инициируемые из скриптов. Например,
XMLHttpRequestи Fetch следуют политике одинакового происхождения. Таким образом, веб-приложение, использующееXMLHttpRequestили Fetch могло делать HTTP-запросы только к своему собственному домену. Чтобы улучшить веб-приложения, разработчики попросили поставщиков браузеров разрешить междоменные запросы.
Чтобы обойти это, была введена концепция CORS (Cross-Origin Resource Sharing). Это позволяет делать запросы из одного домена в другой.
Упрощенное объяснение CORS (для запросов GET) заключается в том, что владелец ресурса (человек, которого вы просите) может добавить заголовок Access-Control-Allow-Origin: google.com к своим ответам API, если они хотят разрешить прохождение запроса AJAX на google.com. Они также могут сделать Access-Control-Allow-Origin: * разрешать запросы со всех доменов. Вкратце, это CORS. (Это немного сложнее, особенно если вы используете другой метод HTTP.)
Если бы вы были разработчиком API, это не было бы проблемой. Вы можете сделать это с помощью всего одной строчки кода. Но в данном случае техническая команда Maximum не столь дружелюбна, поэтому вы не можете попросить их сделать это juuuuust за вас.
Решение
Что мы можем сделать?
- Мы можем подделать (подделать) заголовок «Origin» при отправке нашего запроса - примерно так:
xhr.setRequestHeader("Origin", 'maximum.blog');
Но поскольку ваш браузер такой дружелюбный полицейский, мы получаем следующее:

У нас есть еще один вариант:
- Мы можем попросить кого-нибудь сделать запрос за нас и прислать нам ответ.
И вот что, друзья мои, мы и собираемся делать.
Немного поисков в сети приводит нас к решению, которое идеально подходит для нас (или почти) - CORS-Anywhere. CORS-Anywhere предоставляет прокси-сервер, который передает наш запрос вместе с его заголовками. А исходный код находится на Github, так что вы можете разместить свой собственный.
Итак, если мы изменим наш URL-адрес запроса следующим образом:
xhr.open("GET", "https://cors-anywhere.herokuapp.com/https://maximum.blog/@shalvah/posts");
он должен работать. Однако на самом деле есть одна крошечная кушетка, с которой вы можете столкнуться.
Как видите, API Максимума разрешает запросы только из maximum.blog domain. Но CORS-Anywhere передает ВСЕ наши заголовки, включая наш Origin (который мы не можем изменить, помните?). Поэтому нам нужен способ сообщить CORS-Anywhere использовать целевой URL (maximum.blog) в качестве значения заголовка Origin.
В моем случае я раздвоил репо и добавил опцию конфигурации, которая позволяет вам подделать заголовок Origin, который по умолчанию имеет значение true. Вы можете проверить это здесь". Я также разместил это на Heroku как https://cors-escape.herokuapp.com. [Обновление: размещение прокси-серверов противоречит ToS Heroku, поэтому они больше не доступны.]
Теперь, если мы бежим
xhr.open("GET", "https://cors-escape.herokuapp.com/https://maximum.blog/@shalvah/posts");
все должно работать нормально, и мы получим ожидаемый ответ.
{
"status": "success",
"payload": {
...
}
}
Ура, максимум !!
Альтернативный подход
CORS-Anywhere (или CORS-Escape, как я назвал свою вилку) использует прокси-сервер для отправки наших запросов. (Подробнее о проксировании можно прочитать здесь.)
Проксирование - это что-то вроде передачи вашего запроса в том виде, в котором вы его отправили. Мы могли бы решить эту проблему альтернативным способом, который по-прежнему предполагает, что кто-то другой делает запрос за вас, но на этот раз, вместо использования передачи вашего запроса, сервер делает свой собственный запрос, но с любыми параметрами, которые вы указали.
построил небольшую библиотеку, которая делает это. Вот ядро его кода (NodeJS):
getPostData(req)
.then(body => {
console.log(body);
let options = {
uri: query.url,
headers: body.headers,
method: body.method || 'GET
};
let proxyCallback = (proxyErr, proxyRes, proxyBody) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
res.write(proxyBody);
res.end();
};
request(options, proxyCallback);
})
Функция request отправляет нам вызов API и по завершении выполняет proxyCallback, в котором отправляет нам ответ.
Как работает эта библиотека, все, что вам нужно сделать, это сделать запрос POST на сервер и передать параметр запроса url, содержащий URL-адрес, к которому вы хотите получить доступ. Вы также можете передать тело запроса, указав различные параметры для запроса (нужные заголовки и метод).
xhr.open("POST",
"https://localhost:2000/?url=https://maximum.blog/@shalvah/posts"); // assuming you’re hosting it locally
xhr.setRequestHeader("Content-type", 'application/json’);
let data = {
headers: {
Accept: "application/json",
Origin: "http://maximum.blog"
},
method: 'GET'
};
xhr.send(JSON.stringify(data));
Библиотека также возвращает вам все тело ответа и заголовки.
Вы можете ознакомиться с кодом здесь.
Уф!
Решено. 💃💃
Спасибо за прочтение! Если вам хочется хлопать, нажмите кнопку Хлопок (😊 Акапо Дамилола Фрэнсис). Кроме того, если у вас есть другое решение этой проблемы, как всегда, я хотел бы его услышать. Я всегда прячусь в разделе комментариев, и я в Твиттере по адресу @theshalvah.
Хорошего дня!