Почему отправляется запрос OPTIONS и можно ли его отключить?

416

Я строю веб-API. Я обнаружил, что всякий раз, когда я использую Chrome для POST, GET для моего API, перед реальным запросом всегда отправляется запрос OPTIONS, что довольно раздражает. В настоящее время я получаю сервер, чтобы игнорировать любые запросы ОПЦИИ. Теперь мои вопросы: что хорошего в отправке запроса OPTIONS для удвоения нагрузки на сервер? Есть ли способ полностью остановить браузер от отправки запросов OPTIONS?

Цянь Чен
источник

Ответы:

376

edit 2018-09-13 : добавлены некоторые уточнения относительно этого запроса перед полетом и как его избежать в конце этого ответа.

OPTIONSзапросы - это то, что мы называем pre-flightзапросами Cross-origin resource sharing (CORS).

Они необходимы, когда вы делаете запросы из разных источников в определенных ситуациях.

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

Ваш сервер не должен игнорировать, а обрабатывать эти запросы всякий раз, когда вы пытаетесь выполнить запросы из разных источников.

Хороший ресурс можно найти здесь http://enable-cors.org/

Чтобы справиться с этим, чтобы убедиться, что для любого пути с OPTIONSметодом сервер отправляет ответ с этим заголовком

Access-Control-Allow-Origin: *

Это скажет браузеру, что сервер готов отвечать на запросы любого происхождения.

Для получения дополнительной информации о том, как добавить поддержку CORS на ваш сервер, смотрите следующую блок-схему

http://www.html5rocks.com/static/images/cors_server_flowchart.png

Блок-схема CORS


редактировать 2018-09-13

OPTIONSЗапрос CORS запускается только в некоторых случаях, как описано в документах MDN :

Некоторые запросы не запускают предварительную проверку CORS. В этой статье они называются «простыми запросами», хотя в спецификации Fetch (которая определяет CORS) этот термин не используется. Запрос, который не запускает предварительную проверку CORS - так называемый «простой запрос» - это запрос, который удовлетворяет всем следующим условиям:

Единственные допустимые методы:

  • ПОЛУЧИТЬ
  • ГЛАВА
  • ПОСЛЕ

Помимо заголовков, автоматически устанавливаемых пользовательским агентом (например, Connection, User-Agent или любым другим заголовком с именами, определенными в спецификации Fetch как «имя запрещенного заголовка»), единственными заголовками, которым разрешено быть вручную устанавливаются те, которые спецификация Fetch определяет как «заголовок запроса CORS-safelisted», а именно:

  • принимать
  • Accept-Language
  • Content-Language
  • Content-Type (но обратите внимание на дополнительные требования ниже)
  • DPR
  • Downlink
  • Сохранить данные
  • ВЭкран-Ширина
  • ширина

Единственные допустимые значения для заголовка Content-Type:

  • применение / х-WWW-форм-urlencoded
  • многочастному / форм-данных,
  • текст / обычный

Ни один прослушиватель событий не зарегистрирован ни в одном объекте XMLHttpRequestUpload, используемом в запросе; доступ к ним осуществляется с помощью свойства XMLHttpRequest.upload.

В запросе не используется объект ReadableStream.

Лео Корреа
источник
8
Но нереально установить этот флаг Chrome для всех обычных пользователей.
Цянь Чен
37
Неверно говорить, что предварительные запросы требуются при отправке запросов из разных источников. Предварительные запросы требуются только в определенных ситуациях, например, если вы устанавливаете пользовательские заголовки или делаете запросы, отличные от get, head и post.
Робин Клоуэрс
4
Как ни странно, при выполнении запроса CORS с использованием jQuery библиотека JavaScript специально избегает установки настраиваемого заголовка, а также предупреждает разработчиков: для междоменных запросов, поскольку условия для предварительной проверки похожи на мозаику, мы просто никогда не устанавливайте это, чтобы быть уверенным.
ниже радара
3
Как получится, если я сделаю curlдля API это работает, но при запуске из Chrome я получаю ошибку?
SuperUberDuper
5
@SuperUberDuper, потому что запросы CORS и предполетные проверки связаны с браузером. Вы можете смоделировать CORS, добавив Originзаголовок к вашему запросу, чтобы имитировать, как если бы запрос исходил от определенного хоста (например, yourwebsite.com). Вы также можете смоделировать предварительные запросы, установив HTTP-метод запроса OPTIONSи Access-Control-*заголовки
Лео Корреа
234

Прошел эту проблему, ниже мой вывод на эту проблему и мое решение.

Согласно стратегии CORS (настоятельно рекомендуем вам прочитать об этом) Вы не можете просто заставить браузер прекратить отправку запроса OPTIONS, если он сочтет это необходимым.

Есть два способа обойти это:

  1. Убедитесь, что ваш запрос является «простым запросом»
  2. Набор Access-Control-Max-Ageдля запроса ОПЦИИ

Простой запрос

Простой межсайтовый запрос - это тот, который удовлетворяет всем следующим условиям:

Единственные допустимые методы:

  • ПОЛУЧИТЬ
  • ГЛАВА
  • ПОСЛЕ

Помимо заголовков, автоматически устанавливаемых пользовательским агентом (например, Connection, User-Agent и т. Д.), Единственными заголовками, которые разрешено устанавливать вручную, являются:

  • принимать
  • Accept-Language
  • Content-Language
  • Тип содержимого

Единственные допустимые значения для заголовка Content-Type:

  • применение / х-WWW-форм-urlencoded
  • многочастному / форм-данных,
  • текст / обычный

Простой запрос не вызовет предполетный запрос ОПЦИИ.

Установить кеш для проверки ОПЦИИ

Вы можете установить Access-Control-Max-Ageдля запроса OPTIONS, чтобы он не проверял разрешение снова, пока не истечет срок его действия.

Access-Control-Max-Age дает значение в секундах, в течение которого можно кэшировать ответ на запрос предварительной проверки без отправки другого запроса предварительной проверки.

Ограничение отмечено

  • Для Chrome максимальное количество секунд Access-Control-Max-Ageсоставляет 60010 минут, согласно исходному коду Chrome.
  • Access-Control-Max-Ageработает только для одного ресурса каждый раз, например, GETзапросы с одинаковым URL-путем, но разные запросы будут рассматриваться как разные ресурсы. Таким образом, запрос ко второму ресурсу будет по-прежнему вызывать предварительный запрос.
Neekey
источник
3
Да ... это должен быть принятый ответ и наиболее актуален для вопроса ..!
Раджеш Мбм
7
Спасибо за упоминание Access-Control-Max-Age. Это ключ здесь. Это поможет вам избежать чрезмерных предварительных запросов.
Идрис Мохтарзада
Я использую Axios, чтобы позвонить получить запрос. Где я могу установить Access-Control-Max-Age в запросе axios?
Мохит Шах
+1 Ключевым здесь является заголовок Access-Control-Max-Age. Это должен быть принятый ответ! Я установил 86400 секунд (24 часа) в заголовке, и запрос предварительной проверки пропал!
Revobtz
1
@VitalyZdanevich нет! Не избегайте application/jsonтолько потому, что это делает ваш запрос не «простым» (и, следовательно, запускает CORS). Браузер делает свою работу. Настройте свой сервер так, чтобы он возвращал заголовок, Access-Control-Max-Age: 86400и браузер не будет повторно отправлять запрос OPTIONS в течение 24 часов.
colm.anseo
139

Пожалуйста, направьте этот ответ на вопрос о фактической потребности в предварительном запросе ОПЦИИ: CORS - Какова мотивация введения предварительных запросов?

Чтобы отключить запрос OPTIONS, для ajax-запроса должны быть выполнены следующие условия:

  1. Запрос не устанавливает пользовательские заголовки HTTP, такие как «application / xml» или «application / json» и т. Д.
  2. Метод запроса должен быть одним из GET, HEAD или POST. Если POST, тип содержимого должен быть один из application/x-www-form-urlencoded, multipart/form-dataилиtext/plain

Ссылка: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

device_exec
источник
14
+1 для "пользовательских заголовков HTTP"! В моем случае они вызывали запрос перед полетом. Я реорганизовал запрос на отправку всего, что я отправлял в заголовках, поскольку тело запроса и запросы OPTIONS перестали отправляться.
Андре
21
application/xmlили application/jsonне являются "пользовательскими заголовками HTTP". Заголовок сам по себе будет, Content-Typeи вызов этого заголовка «обычай» будет ввести в заблуждение.
Лео Корреа
1
Удалены пользовательские заголовки HTTP, и это работает как шарм!
Тим Д
47

Когда у вас открыта консоль отладки и Disable Cacheвключена опция, предварительные запросы будут отправляться всегда (т.е. перед каждым запросом). если вы не отключите кеш, предполетный запрос будет отправлен только один раз (на сервер)

Nir
источник
3
о чем я думаю после нескольких часов отладки это было моим решением. кеш отключен из-за отладки консоли.
Mauris
1
Даже если консоль отладки закрыта, отправляются предварительные запросы
Luca Perico
2
Лука: это правда, но дело в том, что «отключить кеш» не действует, когда инструменты разработчика закрыты. предварительные запросы отправляются только один раз (конечно, для каждого сервера), если кэш не отключен, и перед каждым запросом, если кэш отключен.
Nir
Это было действительно полезно.
Анурааг Патил
38

Да, можно избежать запроса параметров. Запрос параметров - это предварительный запрос при отправке (публикации) любых данных в другой домен. Это проблема безопасности браузера. Но мы можем использовать другую технологию: транспортный слой iframe. Я настоятельно рекомендую вам забыть о любой конфигурации CORS и использовать готовое решение, и оно будет работать где угодно.

Посмотрите здесь: https://github.com/jpillora/xdomain

И рабочий пример: http://jpillora.com/xdomain/

XTRUST.ORG
источник
это на самом деле своего рода прокси-сервер?
matanster
15
«Запрос параметров - это предварительный запрос при отправке (публикации) любых данных в другой домен». - Это не правда. Вы можете использовать XHR для отправки любого запроса POST, который вы можете отправить с помощью обычной формы HTML, не вызывая предварительный запрос. Предварительная проверка отправляется только тогда, когда вы начинаете делать то, чего не может сделать форма (например, пользовательские типы контента или дополнительные заголовки запросов).
Квентин
Решение здесь, кажется, полагается на прокладку iframe, которая работает в некоторых случаях, но имеет некоторые серьезные ограничения. Что произойдет, если вы хотите узнать код состояния HTTP ответа или значение другого заголовка ответа HTTP?
Стивен Кросби
5
iFrames не были сделаны для этого.
Ромко
1
Это программная реализация, которая отвечает на последний вопрос: «Есть ли способ полностью запретить браузеру отправлять запросы OPTIONS?». Короче говоря, в Mozilla или Chromium нет возможности отключить его, он реализован в коде, и единственные «рабочие» опции просто обходят это поведение.
мусорщик
15

Для разработчика, который понимает причину его существования, но ему нужен доступ к API, который не обрабатывает вызовы OPTIONS без аутентификации, мне нужен временный ответ, чтобы я мог развиваться локально, пока владелец API не добавит надлежащую поддержку SPA CORS или я не получу прокси-API и работает.

Я обнаружил, что вы можете отключить CORS в Safari и Chrome на Mac.

Отключить ту же политику происхождения в Chrome

Chrome: выйдите из Chrome, откройте терминал и вставьте эту команду: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

Safari: отключение политики одного источника в Safari

Если вы хотите отключить ту же политику происхождения в Safari (у меня есть 9.1.1), вам нужно только включить меню разработчика и выбрать «Отключить ограничения перекрестного происхождения» в меню разработки.

Джозеф Юнке
источник
4
Вы должны уделить больше внимания той части, которая гласит: «Это НИКОГДА не должно быть вечным решением !!!!» , Политика одного источника является очень важной мерой безопасности браузера и никогда не должна отключаться при обычном просмотре Интернета.
Яннис
Хотелось бы, чтобы сеть просто работала таким образом, чтобы мы могли просто запрашивать нужные нам данные с серверов без лишних хлопот.
Джемилойи
14

Как уже упоминалось в предыдущих сообщениях, OPTIONSзапросы есть по причине. Если у вас есть проблема с большим временем отклика от вашего сервера (например, соединение за границей), вы также можете настроить браузер для кэширования предварительных запросов.

Пусть ваш сервер ответит Access-Control-Max-Ageзаголовком, а для запросов, отправляемых в ту же конечную точку, предварительный запрос будет кэширован и больше не будет выполняться.

enpenax
источник
1
Спасибо тебе за это! Тот факт, что OPTIONSзапросы будут кэшироваться с этим заголовком, довольно непрозрачен во всей документации CORS, которую я прочитал.
Джошперри
И кеш вступает в силу только с точно таким же URL-адресом. Мне нужен предварительный кэш на уровне домена, который действительно может уменьшить количество обращений. (CORS глупо!)
чудо
8

Я решил эту проблему, как.

if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
    header("HTTP/1.1 200 OK");
    die();
}

Это только для развития. С этим я жду 9мс и 500мс, а не 8с и 500мс. Я могу сделать это, потому что производственное JS-приложение будет находиться на той же машине, что и производственное, поэтому не будет, OPTIONSно разработка - это моя локальная разработка.

AntiCZ
источник
4

Вы не можете, но вы можете избежать CORS, используя JSONP.

Хосе Мату
источник
2
Вы получаете запрос OPTIONS, только если вы делаете что-то непростое . С помощью JSONP вы можете делать только простые запросы (GET, без пользовательских заголовков, без данных аутентификации), поэтому JSONP не может заменить их здесь.
Квентин
Да, я знаю это, но я не знаю точно требования проекта. Я знаю, что это не просто избежать Cors, но это зависит от проекта. В худшем случае, чтобы избежать CORS, вам нужно передать данные, используя параметры get. Таким образом, JSONP может заменить cors в зависимости от требований проекта (как вы сказали, используя простые запросы)
Jose Mato
Я не понимаю дизайн так называемого предполетного. Что может быть опасным, если клиент решил отправить данные на сервер? Я не вижу смысла удваивать нагрузку на провод.
Цянь Чен
@ElgsQianChen это, вероятно, может ответить на ваш вопрос stackoverflow.com/questions/15381105/…
Лео Корреа
0

Проведя полтора дня, пытаясь решить подобную проблему, я обнаружил, что это связано с IIS .

Мой проект Web API был настроен следующим образом:

// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
    var cors = new EnableCorsAttribute("*", "*", "*");
    config.EnableCors(cors);
    //...
}

У меня не было конкретных параметров конфигурации CORS в узле web.config> system.webServer, как я видел во многих постах

Нет конкретного кода CORS в global.asax или в контроллере в качестве декоратора

Проблема была в настройках пула приложений .

Режим управляемого конвейера был установлен на классический ( изменил его на интегрированный ), а Identity был установлен на Network Service ( изменил его на ApplicationPoolIdentity )

Изменение этих настроек (и обновление пула приложений) исправило это для меня.

Ju66ernaut
источник
-2

Что мне помогло, так это импортировать «github.com/gorilla/handlers», а затем использовать его следующим образом:

router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")

headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})

log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))

Как только я выполнил запрос POST Ajax и прикрепил к нему данные JSON, Chrome всегда добавлял заголовок Content-Type, которого не было в моей предыдущей конфигурации AllowedHeaders.

Kurt
источник
-2

Одно из решений, которое я использовал в прошлом, - скажем, ваш сайт находится на mydomain.com, и вам нужно сделать ajax-запрос к foreigndomain.com

Настройте перезапись IIS с вашего домена на чужой домен - например,

<rewrite>
  <rules>
    <rule name="ForeignRewrite" stopProcessing="true">
        <match url="^api/v1/(.*)$" />
        <action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
    </rule>
  </rules>
</rewrite>

на вашем сайте mydomain.com - вы можете сделать тот же запрос о происхождении, и нет необходимости в запросе опций :)

Дэвид Макелени
источник
-2

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

if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
   set resp.http.Access-Control-Max-Age = "1728000";
   set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
   set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
   set resp.http.Content-Length = "0";
   set resp.http.Content-Type = "text/plain charset=UTF-8";
   set resp.status = 204;
}

}

Рафа Куэстас
источник
-5

Возможно, есть решение (но я его не тестировал): вы можете использовать CSP (Content Security Policy), чтобы включить удаленный домен, и браузеры могут пропустить проверку запроса CORS OPTIONS.

Я, если найду время, протестирую и обновлю этот пост!

CSP: https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy

Спецификация CSP: https://www.w3.org/TR/CSP/

Арно Турнье
источник
Я только что проверил, и это не работает, CORS все еще требуется после CSP для приема запроса xhr ...
Арно Турнье