Запрос HTTP OPTIONS на сайтах Azure не удается из-за CORS

Недавно я переместил наши серверы из Rackspace CloudSites (работает на Apache/Linux) на веб-сайты Windows Azure. После миграции все запросы JQuery AJAX в нашем REST API начали сбой из-за CORS.

Мы используем пользовательские заголовки, поэтому jQuery делает запрос перед полетом HTTP OPTIONS перед запуском фактических вызовов API. Проблема заключается в том, что запрос OPTIONS, похоже, не доходит до моего PHP-кода и вместо этого возвращен каким-то другим объектом (очевидно, веб-сервером), с которым я, похоже, не контролирую.

Я использую следующие заголовки уже пару лет, поэтому я уверен, что проблема не в PHP-коде:

<?php
    $this->output->set_header("Access-Control-Allow-Origin: *");
    $this->output->set_header("Access-Control-Allow-Methods: GET,POST,DELETE,HEAD,PUT,OPTIONS");
    $this->output->set_header("Access-Control-Allow-Headers: X-Olaround-Debug-Mode, Authorization, Accept");
    $this->output->set_header("Access-Control-Expose-Headers: X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" );
?>

Я предполагаю, что проблема специфична для Azure Websites, так как код, похоже, отлично работает на моей машине разработки (Windows 8/IIS 8.0). Я новичок в Azure (и в основном на базе Windows), поэтому я почти не знаю, как подойти и отладить эту проблему, поскольку веб-сайты Azure позволяют очень минимально контролировать.

Ответ 1

Я решил опубликовать полное решение этой проблемы, поскольку уже предоставленные ответы (хотя и технически корректные) не работают в этом конкретном случае для меня. Трюк заключался в следующем:

1. Добавьте <customHeaders> в <httpProtocol> в web.config

Подобно @hcoat, также предложенному выше, добавление system.webServer.httpProtocol.customHeaders было первым шагом для решения проблемы (я уже пробовал это раньше, но это не сработало). Добавьте все пользовательские заголовки и методы HTTP, которые необходимо установить для CORS здесь.

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Olaround-Debug-Mode, Authorization, Accept" />
        <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
    </customHeaders>
</httpProtocol>

2. Переопределите обработчик по умолчанию для PHP и удалите OPTIONSVerbHandler

Следующий шаг (решение, предоставленное @Bing Han), заключается в том, чтобы удалить стандартную OPTIONSVerbHandler, определенную в IIS, а также установить пользовательский обработчик PHP54_via_FastCGI, который принимает ваши дополнительные HTTP-методы. Обработчик по умолчанию работает только с запросами GET, POST и HEAD.

<handlers>
    <remove name="OPTIONSVerbHandler" />
    <remove name="PHP54_via_FastCGI" />
    <add name="PHP54_via_FastCGI" path="*.php" verb="GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" requireAccess="Script" />
</handlers>

Взгляните на этот пост для более подробной информации о внутренней работе.

3. Удалите все заголовки ответов, установленные через код приложения

Это был последний фрагмент головоломки, которая вызывала большинство проблем. Поскольку IIS уже добавлял <customHeaders>, фрагмент кода PHP, который я использовал в вышеперечисленном вопросе, дублировал их. Это вызвало проблемы на уровне браузера, которые плохо реагировали на несколько заголовков того же типа.

Последний web.config, который работал для этой проблемы

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Imported Rule 1" stopProcessing="true">
                    <match url="^(.*)$" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{R:1}" pattern="^(dir_path\.php|lolaround|lolaround\.php|app_assets)" ignoreCase="false" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="lolaround.php/{R:1}" />
                </rule>
                <rule name="Imported Rule 2" stopProcessing="true">
                    <match url="lolaround/(.*)" ignoreCase="false" />
                    <action type="Rewrite" url="/lolaround.php/{R:1}" />
                </rule>
            </rules>
        </rewrite>
        <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Origin" value="*" />
                <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
                <add name="Access-Control-Allow-Headers" value="Origin, X-Olaround-Debug-Mode, Authorization, Accept" />
                <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
            </customHeaders>
        </httpProtocol>
        <handlers>
            <remove name="OPTIONSVerbHandler" />
            <remove name="PHP54_via_FastCGI" />
            <add name="PHP54_via_FastCGI" path="*.php" verb="GET, PUT, POST, HEAD, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" requireAccess="Script" />
        </handlers>
    </system.webServer>
</configuration>

Примечание. Хотя оба ответа @hcoat и @Bing Han были полезны в этой задаче, я могу только наградить щедрость одному из них. Я решил отдать его @Bing Han, потому что его ответ приблизил меня к решению (и я не смог найти способ добавить собственный обработчик PHP из собственного поиска).

Обновить. Я также отредактировал ответ, чтобы добавить поддержку метода DELETE HTTP, который отсутствовал в исходном ответе.

Ответ 2

Запрос HTTP OPTIONS завершается с ошибкой, потому что обработчик PHP-CGI по умолчанию не обрабатывает глагол "OPTIONS".

Добавьте следующий код в файл web.config, чтобы решить проблему.

<configuration>
  <system.webServer>
    <!-- 
      Some other settings 
    -->
    <handlers>
      <remove name="OPTIONSVerbHandler" />
      <remove name="PHP54_via_FastCGI" />
      <add name="PHP54_via_FastCGI" path="*.php" verb="GET,HEAD,POST,OPTIONS" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" />
    </handlers>
  </system.webServer>
</configuration>

У меня есть сообщение в блоге об этом: http://tekblg.blogspot.sg/2013/09/azure-websites-php-cross-domain-request.html

Ответ 3

На сервере Windows вы не можете полагаться на заголовки php для CORS. Вам нужно создать web.config в корне сайта или корневом каталоге приложения, содержащем что-то вроде следующего.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.webServer>
   <httpProtocol>
     <customHeaders>
       <add name="Access-Control-Allow-Origin" value="*" />
       <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
       <add name="Access-Control-Allow-Headers" value="X-Olaround-Debug-Mode, Authorization, Accept" />
       <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
     </customHeaders>
   </httpProtocol>
 </system.webServer>
</configuration>

Этот процесс сопоставим с настройкой файла .htaccess, тем самым вы можете просто создать его там, где вам нужно, не перенастраивая ваш сервер.