Серия статей:
- [Младший брат, ты хочешь знать о кросс-доменном] JSONP
- [Брат, ты хочешь знать о кросс-доменном] Основы CORS
- [Брат, ты хочешь знать о междоменных ресурсах] Расширенные статьи CORS
- [Брат, ты хочешь знать о кросс-доменном] обратном прокси NGINX
- [Брат, ты хочешь знать о кросс-доменном] ServerProxy
Рождение предполетного запроса
существуетпредыдущий постВ итоге мы обнаружили, что при использовании CORS для достижения кросс-доменности иногда отправляются два запроса.
一个 OPTIONS 一个正常请求
, что это за ВАРИАНТЫ?
Ниже объяснение от MDN
Как мы все знаем, более популярная парадигма проектирования серверных API — restful (до 8 декабря 2018 г.) В Restful используются разные HTTP-МЕТОДЫ для идентификации серверных CURD, а для тех HTTP-МЕТОДов, которые могут обновляться обратно, end data, Для междоменных запросов браузер должен сначала согласовать с сервером, имеет ли текущее доменное имя полномочия для выполнения соответствующего CURD.Таким образом, этот тип OPTIONS预检请求
родился.Тогда здесь возникает проблема可能对服务器数据产生副作用的 HTTP 请求方法
Есть ли такие? Не знаю, связано ли это с этим, капитан ТИМ исследует путь для нас 😄
Простой запрос против сложного запроса
В механизме CORS запрос делится на简单请求
а также复杂请求
, HTTP-запрос, который хочет сделать себя простым запросом, должен соответствовать следующим условиям:
- Во-первых, ограничение метода запроса: метод запроса (метод) может быть только
GET POST HEAD
один из трех - Второе — ограничение полей заголовка запроса: поля заголовка запроса должны быть включены в следующие наборы, в том числе:
Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width
. - Второе — ограничение значения заголовка запроса: когда заголовок запроса содержит
Content-Type
, его значение должно бытьtext/plain multipart/form-data application/x-www-form-urlencoded(这个是 form 提交默认的 Content-Type)
один из трех.
Подводя итог, можно сказать, что до тех пор, пока запрос, отправленный клиентским интерфейсом, соответствует трем вышеуказанным условиям, запрос, который вы отправляете, является простым запросом.Так что же такое сложный запрос?Ответ: Пока это не простой запрос, это сложный запрос, три условия для получения ^_^.
Позвольте мне открыть вам еще один секрет, все простые запросы на междоменный доступ не будут запускать предварительные запросы Это патент для сложных запросов...
Что сделал предварительный запрос 😳
Для сложных запросов перед междоменным доступом аутентификация всегда выполняется через запрос предварительной проверки. Итак, как выглядит процесс аутентификации? Давайте вместе изучим этот шаг.
- Во-первых, откройтеКод из предыдущего раздела
- Выполнить отдельно
node ./be/cors/index.js
live-server ./fe/cors
Запустите серверную службу и внешний веб-контейнер. - После того, как браузер откроется автоматически, откройте консоль, перейдите на вкладку «Сеть» и обновите браузер.Если ничего другого, это то, что вы видите
- Нажмите на первый запрос localhost и посмотрите подробности
Нетрудно обнаружить, что поля, отмеченные в заголовке ответа, написаны в нашем back-end проекте.
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Allow-Methods', 'PUT');
response.setHeader('Access-Control-Allow-Headers', 'token');
response.setHeader('Access-Control-Max-Age', 5);
Переписка один на один, неспроста 😄 Так что же означают двойки, отмеченные в шапке запроса?
Когда браузер получает отправленную нами инструкцию междоменного запроса, он автоматически определяет, является ли наш запрос междоменным запросом, и если да, то выдает предварительный запрос.Информация заголовка запроса предварительного запроса также основана на на наш Информация о запросе добавляется автоматически. В примере проекта, потому что наш запросPUT
type, поэтому он будет добавлен в предварительный запросAccess-Control-Allow-Methods: PUT
чтобы проконсультироваться с самим сервером, может ли он отправить ему этот тип запроса Аналогично, поскольку наш запрос имеет настраиваемые заголовки запросаtoken
Таким образом, в предварительном запросе браузеру необходимо согласовать с сервером добавление настраиваемых заголовков запроса.Только когда предварительное согласование запроса между браузером и сервером пройдено, браузер продолжит отправлять реальныеAJAX
просить.
Босс говорит, что я не хочу видеть избыточные запросы
На работе начальник часто не разбирается в технике.Босс который видит консоль обычно мастер.В условиях такой ситуации когда апи отправляет два запроса,программист может улыбнется и пройдет,а начальник нет Если так, интерфейс нужно запросить один раз.圈圈的圈
Порекомендуй начальству междоменную статью,чтоб младший брат тоже понял?Рассчитывается,что тебя уволят огнем.Что с опухолью?
Столкнувшись с этой ситуацией, есть два решения.
- Первый, вы можете обсудить это с младшим братом бэкенда.Измените интерфейс на простой запрос, и проблема запроса перед полетом будет решена легко.
- Однако иногда никто не хочет менять написанный код, бэкенд-братишка не слушается, в этом случае
Access-Control-Max-Age
Это удобно. Этот заголовок ответа означает срок действия предварительного запроса. Если вы снова получите доступ к интерфейсу через домен в течение указанного времени, предварительный запрос не нужен.秒
, Если мы напишем действительное время очень долго, то похоже, что предварительный запрос был удален ^_^. - Кроме того, ваш начальник не знает технологии и вслепую командует J8.Господи, я увольняюсь.Конечно, это решение не рекомендуется.
ПС: используйтеAccess-Control-Max-Age
Механизм аналогичен кешу, поэтому не очищайте кеш при представлении боссу Не проверяйте Сеть подdisable cache
Не говори, это все слезы...
Мы не можем позволить всем посетить
пройти черезAccess-Control-Allow-Origin
, Вы можете установить список доменных имен, к которым можно получить доступ через домены в бэкэнде,*
Это означает, что все доменные имена могут получить доступ к нашему серверу через домены, что на самом деле представляет собой скрытую опасность. Из соображений безопасности мы ограничиваем доменные имена, к которым можно получить доступ через домены, до наших известных доменных имен. Старые правила.
внутренний код
// 修改一行代码, 一定要添加协议哟
response.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');
Доступ через браузер после модификацииhttp://127.0.0.1:8080
Что делать, если вы хотите открыть междоменный доступ для нескольких доменных имен?
Если у нас есть несколько бизнес-доменов, которым требуется доступ к одному и тому же серверу из разных доменов, мы можем сохранить список разрешенных доменных имен в массиве.После получения запроса сначала определите, находится ли текущее запрошенное доменное имя в нашем списке разрешенных доменных имен. , и если да, добавьте его прямо в заголовок ответаAccess-Control-Allow-Origin
Вниз.
внутренний код
const http = require('http');
const PORT = 8888;
// 协议名必填, 如果同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'https://www.baidu.com'];
// 创建一个 http 服务
const server = http.createServer((request, response) => {
const { headers: { origin } } = request;
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin);
}
response.setHeader('Access-Control-Allow-Methods', 'PUT');
response.setHeader('Access-Control-Allow-Headers', 'token');
response.setHeader('Access-Control-Max-Age', 5);
response.end("{name: 'quanquan', friend: 'guiling'}");
});
// 启动服务, 监听端口
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
});
В настоящее времякод, первое посещениеhttp://127.0.0.1:8080 Результат ответа печатается успешно без каких-либо проблем.
следующий визитwww.baidu.com, открыть консоль, выполнить
xhr = new XMLHttpRequest()
xhr.open('GET', 'http://localhost:8888')
xhr.onreadystatechange = () => {
xhr.status === 200 && xhr.readyState === 4 && console.log(xhr.responseText)
}
xhr.send()
Ошибки нет, возвращенный результат успешно печатается.Успешно...
Почему в вашем запросе нет файла cookie?
В общем, междоменное ajax OR, испускаемое из внешнего интерфейсаfetchЗапрос не будет содержать файлы cookie. Тем не менее, серверному младшему брату они все равно нужны. Что делать? Добавьте их.
Интерфейсный код:
// 在 xhr.send 之前添加这一行
xhr.withCredentials = true;
После добавления обновите браузер.
По поводу этой ошибки не знаю, есть ли у вас что сказать, да и мне нечего сказать...
Внутренний код:
const http = require('http');
const PORT = 8888;
// 协议名必填, 如果同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'http://localhost:8080', 'https://www.baidu.com'];
// 创建一个 http 服务
const server = http.createServer((request, response) => {
const { method, headers: { origin, cookie } } = request;
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin);
}
response.setHeader('Access-Control-Allow-Methods', 'PUT');
// 允许前端请求携带 Cookie
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Headers', 'token');
if (method === 'OPTIONS') {
console.log('预检请求');
} else if (!cookie) {
// 如果不存在 Cookie 就设置 Cookie
response.setHeader('Set-Cookie', 'quanquan=fe');
}
response.end("{name: 'quanquan', friend: 'guiling'}");
});
// 启动服务, 监听端口
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
});
В настоящее времякод, снова зайдите в браузер, чтобы посмотреть.
Еще одно печенье
Файл cookie был включен в запрос
Как видно из анимации ниже, наша клиентская и серверная доставка файлов cookie работает очень гладко.
Я вернул вам Токен в шапке ответа, вы его достаете и кладете в шапку запроса
В работе мы часто сталкиваемся со случаями, когда бэкенд ставит какие-то идентификаторы в заголовок ответа и возвращает его фронтенду, например, при входе пользователя, а бэкенд возвращает уникальный идентификатор пользователя в заголовке ответа. для получения этого идентификатора и последующих запросов нужно поставить этот идентификатор Поместите его в заголовок запроса для проверки личности пользователя.
Сначала мы модифицируем внутренний код:
// 在 response.end() 前添加这一行
response.setHeader('token', 'quanquan');
Измените внешний код:
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
// 打印响应数据时同时打印所有响应头
console.log(xhr.getAllResponseHeaders())
}
}
После модификациикод, посмотрите в браузере.
console.log выводит пустые строки
Но серверная часть возвращает поле токена заголовка ответа на вкладке «Сеть». Я запутался...
оказалось,Access-Control-
Серия также имеет заголовок ответаAccess-Control-Expose-Headers
, мы кодируем в бэкендеresponse.end(...)
добавить передresponse.setHeader('Access-Control-Expose-Headers', 'token');
Проверьте в браузере еще раз
Это сработало 😄.
Предварительный запрос не возвращает содержимое, помещенное
Результаты нашего ответа должны были быть возвращены в официальном запросе, но мы смотрим на детали возврата запроса предварительной проверки и обнаруживаем, что
Предполетный запрос — это только анализ на уровне браузера, а интерфейсный код вообще не может его получить. Контент здесь — просто пустая трата пропускной способности и пользовательского трафика. Так что давайте изменим его. Предпечатный запрос больше не возвращает контент .
Внутренний код:
const http = require('http');
const PORT = 8888;
// 协议名必填, 如果同时存在 http 和 https 就写两条s
const allowOrigin = ['http://127.0.0.1:8080', 'http://localhost:8080', 'https://www.baidu.com'];
// 创建一个 http 服务
const server = http.createServer((request, response) => {
const { method, headers: { origin, cookie } } = request;
if (allowOrigin.includes(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin);
}
response.setHeader('Access-Control-Allow-Methods', 'PUT');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Headers', 'token');
response.setHeader('Access-Control-Expose-Headers', 'token');
response.setHeader('token', 'quanquan');
if (method === 'OPTIONS') {
response.writeHead(204);
response.end('');
} else if (!cookie) {
response.setHeader('Set-Cookie', 'quanquan=fe');
}
response.end("{name: 'quanquan', friend: 'guiling'}");
});
// 启动服务, 监听端口
server.listen(PORT, () => {
console.log('服务启动成功, 正在监听: ', PORT);
});
В настоящее времякод, проверяй, давай не проверяй.Так хорошо 😄
Следующее базовое уведомление: даже если первые два междоменных решения готовы, многие друзья жалуются, что jsonp слишком устарел, cors слишком проблематичен... Тогда давайте попробуем это в следующем разделе.反向代理
, See you