[Брат, ты хочешь узнать о междоменных ресурсах] Расширенные статьи CORS

Node.js внешний интерфейс

Серия статей:

Рождение предполетного запроса

существуетпредыдущий постВ итоге мы обнаружили, что при использовании CORS для достижения кросс-доменности иногда отправляются два запроса.一个 OPTIONS 一个正常请求, что это за ВАРИАНТЫ?

Ниже объяснение от MDN2018-12-08-09-17-13

Как мы все знаем, более популярная парадигма проектирования серверных API — restful (до 8 декабря 2018 г.) В Restful используются разные HTTP-МЕТОДЫ для идентификации серверных CURD, а для тех HTTP-МЕТОДов, которые могут обновляться обратно, end data, Для междоменных запросов браузер должен сначала согласовать с сервером, имеет ли текущее доменное имя полномочия для выполнения соответствующего CURD.Таким образом, этот тип OPTIONS预检请求родился.Тогда здесь возникает проблема可能对服务器数据产生副作用的 HTTP 请求方法Есть ли такие? Не знаю, связано ли это с этим, капитан ТИМ исследует путь для нас 😄

2018-12-08-09-32-33

Простой запрос против сложного запроса

В механизме 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)один из трех.

Подводя итог, можно сказать, что до тех пор, пока запрос, отправленный клиентским интерфейсом, соответствует трем вышеуказанным условиям, запрос, который вы отправляете, является простым запросом.Так что же такое сложный запрос?Ответ: Пока это не простой запрос, это сложный запрос, три условия для получения ^_^.

2018-12-08-09-58-34

Позвольте мне открыть вам еще один секрет, все простые запросы на междоменный доступ не будут запускать предварительные запросы Это патент для сложных запросов...

Что сделал предварительный запрос 😳

Для сложных запросов перед междоменным доступом аутентификация всегда выполняется через запрос предварительной проверки. Итак, как выглядит процесс аутентификации? Давайте вместе изучим этот шаг.

  • Во-первых, откройтеКод из предыдущего раздела
  • Выполнить отдельноnode ./be/cors/index.js live-server ./fe/corsЗапустите серверную службу и внешний веб-контейнер.
  • После того, как браузер откроется автоматически, откройте консоль, перейдите на вкладку «Сеть» и обновите браузер.Если ничего другого, это то, что вы видите

2018-12-08-10-09-38

  • Нажмите на первый запрос localhost и посмотрите подробности

2018-12-08-10-22-25Нетрудно обнаружить, что поля, отмеченные в заголовке ответа, написаны в нашем 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);

Переписка один на один, неспроста 😄 Так что же означают двойки, отмеченные в шапке запроса?

Когда браузер получает отправленную нами инструкцию междоменного запроса, он автоматически определяет, является ли наш запрос междоменным запросом, и если да, то выдает предварительный запрос.Информация заголовка запроса предварительного запроса также основана на на наш Информация о запросе добавляется автоматически. В примере проекта, потому что наш запросPUTtype, поэтому он будет добавлен в предварительный запросAccess-Control-Allow-Methods: PUTчтобы проконсультироваться с самим сервером, может ли он отправить ему этот тип запроса Аналогично, поскольку наш запрос имеет настраиваемые заголовки запросаtokenТаким образом, в предварительном запросе браузеру необходимо согласовать с сервером добавление настраиваемых заголовков запроса.Только когда предварительное согласование запроса между браузером и сервером пройдено, браузер продолжит отправлять реальныеAJAXпросить.

Босс говорит, что я не хочу видеть избыточные запросы

На работе начальник часто не разбирается в технике.Босс который видит консоль обычно мастер.В условиях такой ситуации когда апи отправляет два запроса,программист может улыбнется и пройдет,а начальник нет Если так, интерфейс нужно запросить один раз.圈圈的圈Порекомендуй начальству междоменную статью,чтоб младший брат тоже понял?Рассчитывается,что тебя уволят огнем.Что с опухолью?

2018-12-08-10-42-18

Столкнувшись с этой ситуацией, есть два решения.

  • Первый, вы можете обсудить это с младшим братом бэкенда.Измените интерфейс на простой запрос, и проблема запроса перед полетом будет решена легко.
  • Однако иногда никто не хочет менять написанный код, бэкенд-братишка не слушается, в этом случаеAccess-Control-Max-AgeЭто удобно. Этот заголовок ответа означает срок действия предварительного запроса. Если вы снова получите доступ к интерфейсу через домен в течение указанного времени, предварительный запрос не нужен., Если мы напишем действительное время очень долго, то похоже, что предварительный запрос был удален ^_^.
  • Кроме того, ваш начальник не знает технологии и вслепую командует J8.Господи, я увольняюсь.Конечно, это решение не рекомендуется.

ПС: используйтеAccess-Control-Max-AgeМеханизм аналогичен кешу, поэтому не очищайте кеш при представлении боссу Не проверяйте Сеть подdisable cacheНе говори, это все слезы...2018-12-08-11-13-21

Мы не можем позволить всем посетить

пройти черезAccess-Control-Allow-Origin, Вы можете установить список доменных имен, к которым можно получить доступ через домены в бэкэнде,*Это означает, что все доменные имена могут получить доступ к нашему серверу через домены, что на самом деле представляет собой скрытую опасность. Из соображений безопасности мы ограничиваем доменные имена, к которым можно получить доступ через домены, до наших известных доменных имен. Старые правила.

2018-12-08-11-24-04

внутренний код

// 修改一行代码, 一定要添加协议哟
response.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8080');

Доступ через браузер после модификацииhttp://127.0.0.1:8080 2018-12-08-11-27-57

Что делать, если вы хотите открыть междоменный доступ для нескольких доменных имен?

Если у нас есть несколько бизнес-доменов, которым требуется доступ к одному и тому же серверу из разных доменов, мы можем сохранить список разрешенных доменных имен в массиве.После получения запроса сначала определите, находится ли текущее запрошенное доменное имя в нашем списке разрешенных доменных имен. , и если да, добавьте его прямо в заголовок ответа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 2018-12-08-11-46-03Результат ответа печатается успешно без каких-либо проблем.

следующий визит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()

2018-12-08-11-54-13

Ошибки нет, возвращенный результат успешно печатается.Успешно...

Почему в вашем запросе нет файла cookie?

В общем, междоменное ajax OR, испускаемое из внешнего интерфейсаfetchЗапрос не будет содержать файлы cookie. Тем не менее, серверному младшему брату они все равно нужны. Что делать? Добавьте их.

2018-12-08-12-05-05

Интерфейсный код:

// 在 xhr.send 之前添加这一行
xhr.withCredentials = true;

После добавления обновите браузер.

2018-12-08-12-07-24

По поводу этой ошибки не знаю, есть ли у вас что сказать, да и мне нечего сказать...

Внутренний код:

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);
});

В настоящее времякод, снова зайдите в браузер, чтобы посмотреть.

Еще одно печенье2018-12-08-12-37-50

Файл cookie был включен в запрос2018-12-08-12-37-26

Как видно из анимации ниже, наша клиентская и серверная доставка файлов cookie работает очень гладко.cookie-2134567

Я вернул вам Токен в шапке ответа, вы его достаете и кладете в шапку запроса

В работе мы часто сталкиваемся со случаями, когда бэкенд ставит какие-то идентификаторы в заголовок ответа и возвращает его фронтенду, например, при входе пользователя, а бэкенд возвращает уникальный идентификатор пользователя в заголовке ответа. для получения этого идентификатора и последующих запросов нужно поставить этот идентификатор Поместите его в заголовок запроса для проверки личности пользователя.

Сначала мы модифицируем внутренний код:

// 在 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 выводит пустые строки2018-12-08-13-26-21

Но серверная часть возвращает поле токена заголовка ответа на вкладке «Сеть». Я запутался...2018-12-08-13-28-41

оказалось,Access-Control-Серия также имеет заголовок ответаAccess-Control-Expose-Headers, мы кодируем в бэкендеresponse.end(...)добавить передresponse.setHeader('Access-Control-Expose-Headers', 'token');Проверьте в браузере еще раз2018-12-08-13-31-40

Это сработало 😄.

Предварительный запрос не возвращает содержимое, помещенное

Результаты нашего ответа должны были быть возвращены в официальном запросе, но мы смотрим на детали возврата запроса предварительной проверки и обнаруживаем, что2018-12-08-13-34-09

Предполетный запрос — это только анализ на уровне браузера, а интерфейсный код вообще не может его получить. Контент здесь — просто пустая трата пропускной способности и пользовательского трафика. Так что давайте изменим его. Предпечатный запрос больше не возвращает контент .

Внутренний код:

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