AngularJS выполняет HTTP-запрос OPTIONS для ресурса с несколькими источниками


264

Я пытаюсь настроить AngularJS для связи с ресурсом из разных источников, где узел ресурсов, который доставляет мои файлы шаблонов, находится в другом домене, и поэтому запрос XHR, который выполняет angular, должен быть междоменным. Я добавил соответствующий заголовок CORS на свой сервер для HTTP-запроса, чтобы он работал, но, похоже, он не работает. Проблема в том, что когда я проверяю HTTP-запросы в моем браузере (chrome), запрос, отправленный в файл ресурса, является запросом OPTIONS (это должен быть запрос GET).

Я не уверен, является ли это ошибкой в ​​AngularJS или мне нужно что-то настроить. Из того, что я понимаю, оболочка XHR не может сделать HTTP-запрос OPTIONS, поэтому похоже, что браузер пытается выяснить, «разрешено» ли загружать ресурс, прежде чем он выполнит запрос GET. Если это так, то мне нужно также установить заголовок CORS (Access-Control-Allow-Origin: http://asset.host ... ) С хостом ресурса?

Ответы:


226

Запрос OPTIONS ни в коем случае не является ошибкой AngularJS, именно так стандарт Cross-Origin Resource Sharing предписывает браузерам себя вести. Пожалуйста, обратитесь к этому документу: https://developer.mozilla.org/en-US/docs/HTTP_access_control , где в разделе «Обзор» говорится:

Стандарт совместного использования ресурсов перекрестного происхождения работает путем добавления новых заголовков HTTP, которые позволяют серверам описывать набор источников, которым разрешено читать эту информацию с помощью веб-браузера. Кроме того, для методов HTTP-запросов, которые могут вызывать побочные эффекты для пользовательских данных (в частности, для HTTP-методов, отличных от GET, или для использования POST с определенными типами MIME). Спецификация требует, чтобы браузеры «предварительно отправляли» запрос, запрашивая поддерживаемые методы у сервера с заголовком запроса HTTP OPTIONS, а затем, после «одобрения» с сервера, отправляли фактический запрос с фактическим методом HTTP-запроса. Серверы также могут уведомлять клиентов о необходимости отправки «учетных данных» (включая файлы cookie и данные HTTP-аутентификации) с запросами.

Очень сложно предоставить общее решение, которое работало бы для всех WWW-серверов, так как настройка будет зависеть от самого сервера и HTTP-глаголов, которые вы намереваетесь поддерживать. Я бы посоветовал вам ознакомиться с этой замечательной статьей ( http://www.html5rocks.com/en/tutorials/cors/ ), которая содержит гораздо больше информации о точных заголовках, которые должны быть отправлены сервером.


23
@matsko Можете ли вы рассказать, что вы сделали, чтобы решить эту проблему? Я сталкиваюсь с той же проблемой, когда запрос AngularJS $resource POST генерирует запрос OPTIONS для моего внутреннего сервера ExpressJS (на том же хосте, но на другом порту).
DB

6
Для всех избирателей - почти невозможно обеспечить точную настройку конфигурации для всех веб-серверов - ответ в этом случае займет 10 страниц. Вместо этого я связался со статьей, которая предоставляет больше деталей.
pkozlowski.opensource

5
Вы правы в том, что вы не можете прописать ответ - вам нужно добавить заголовки в ваш ответ OPTIONS, который охватывает все заголовки, которые запрашивает браузер, в моем случае, используя chrome, это были заголовки 'accept' и 'x -requested-с. В Chrome я выяснил, что мне нужно добавить, посмотрев сетевой запрос и увидев, что запрашивает Chrome. Я использую nodejs / expressjs в качестве резервного, поэтому я создал сервис, который возвратил ответ на запрос OPTIONS, который охватывает все требуемые заголовки. -1 потому что я не мог использовать этот ответ, чтобы понять, что делать, пришлось самому разобраться.
Эд Сайкс

2
Я знаю, что это 2+ года спустя, но ... OP обращается к GET-запросам несколько раз (выделено мной): «[...] запрос, отправленный в файл актива, является OPTIONS-запросом ( это должен быть GET). запрос ). » и «браузер пытается выяснить,« разрешено ли »загружать ресурс, прежде чем он выполнит запрос GET ». Как это не может быть ошибка AngularJS тогда? Разве предварительные запросы не должны быть запрещены для GET?
polettix

4
@polettix, даже запрос GET может вызвать предполетный запрос в браузере, если используются пользовательские заголовки. Проверьте «не такие простые запросы» в статье, на которую я ссылаюсь : html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Еще раз, это механизм браузера , а не AngularJS.
pkozlowski.opensource

70

Для Angular 1.2.0rc1 + вам нужно добавить resourceUrlWhitelist.

1.2: в выпускной версии добавлена ​​функция escapeForRegexp, поэтому вам больше не нужно экранировать строки. Вы можете просто добавить URL-адрес напрямую

'http://sub*.assets.example.com/**' 

не забудьте добавить ** для подпапок. Вот рабочий jsbin для 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Если вы все еще используете версию rc, решение Angular 1.2.0rc1 выглядит следующим образом:

.config(['$sceDelegateProvider', function($sceDelegateProvider) {
     $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?yourdomain.com/]);
 }])

Вот пример jsbin, где он работает для 1.2.0rc1: http://jsbin.com/olavok/144/edit


До 1.2: Для более старых версий (ref http://better-inter.net/enabling-cors-in-angular-js/ ) вам необходимо добавить следующие 2 строки в вашу конфигурацию:

$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Вот пример jsbin, где он работает для версий до 1.2: http://jsbin.com/olavok/11/edit


Это сработало для меня. Вот подходящая магия для серверной стороны с помощью nodejs / express: gist.github.com/dirkk0/5967221
dirkk0

14
Это работает только для GET-запроса, но не может найти решение для POST-запроса для междоменного домена.
Пн

2
Сочетание этого ответа с ответом user2304582 выше должно работать для запросов POST. Вы должны указать своему серверу принимать запросы POST от внешнего сервера, который его отправил
Чарли Мартин

Это не похоже, что это будет работать само по себе для меня ... так что -1. Вам нужно что-то по тому же URL, что будет отвечать на запрос OPTIONS как часть проверки перед полетом. Можете ли вы объяснить, почему это будет работать?
Эд Сайкс

1
@EdSykes, я обновил ответ для 1.2 и добавил рабочий пример jsbin. Надеюсь, это должно решить это за вас. Убедитесь, что вы нажали кнопку «Запустить с JS».
JStark

62

НОТА: Не уверен, что он работает с последней версией Angular.

ОРИГИНАЛ:

Также возможно переопределить запрос OPTIONS (был протестирован только в Chrome):

app.config(['$httpProvider', function ($httpProvider) {
  //Reset headers to avoid OPTIONS request (aka preflight)
  $httpProvider.defaults.headers.common = {};
  $httpProvider.defaults.headers.post = {};
  $httpProvider.defaults.headers.put = {};
  $httpProvider.defaults.headers.patch = {};
}]);

1
У меня хорошо работает с Chrome, FF и IE 10. IE 9 и ниже должен быть проверен на компьютерах с Windows 7 или XP.
DOM

3
В качестве альтернативы можно установить это только для метода $ resource, а не глобально:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r

3
Есть ли улов с этим? Это кажется таким простым, и все же ответ так глубоко в теме? редактировать: не работает вообще.
Себастьянсо

Куда идет этот код? в app.js, controller.js или где именно.
Мурлидхар Фичадия

Этот код ничего не делает для запроса get. Я добавил отдельное правило для get, а также в моей конфигурации
kushalvm

34

Ваш сервис должен ответить на OPTIONSзапрос с такими заголовками:

Access-Control-Allow-Origin: [the same origin from the request]
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: [the same ACCESS-CONTROL-REQUEST-HEADERS from request]

Вот хороший документ: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server


1
Чтобы подробнее остановиться на части [тот же ACCESS-CONTROL-REQUEST-HEADERS from request], о которой я упоминал в другом ответе, вам нужно посмотреть на запрос OPTIONS, добавляемый вашим браузером. Затем добавьте эти заголовки в создаваемую службу (или веб-сервер). В выражениях, которые выглядели так: ejs.options (' ', function (request, response) {response.header ('Access-Control-Allow-Origin', ' '); response.header ('Access-Control-Allow-Methods ',' GET, PUT, POST, DELETE '); response.header (' Access-Control-Allow-Headers ',' Content-Type, Authorization, accept, x-required-with '); response.send (); });
Эд Сайкс

1
Комментарий Эд Сайкс очень точный, имейте в виду, что заголовки, отправленные в ответе OPTIONS, и заголовки, отправленные в ответе POST, должны быть точно такими же, например: Access-Control-Allow-Origin: * не совпадает с Access- Control-Allow-Origin: * из-за пробелов.
Люсия

20

В том же документе говорится

В отличие от простых запросов (обсужденных выше), «предварительно выданные» запросы сначала отправляют заголовок запроса HTTP OPTIONS ресурсу в другом домене, чтобы определить, является ли фактический запрос безопасным для отправки. Межсайтовые запросы предварительно просматриваются следующим образом, поскольку они могут иметь значение для пользовательских данных. В частности, запрос предварительно просвечивается, если:

Он использует методы, отличные от GET или POST. Кроме того, если POST используется для отправки данных запроса с Content-Type, отличным от application / x-www-form-urlencoded, multipart / form-data или text / plain, например, если запрос POST отправляет полезную нагрузку XML на сервер используя application / xml или text / xml, запрос предварительно просвечивается.

Он устанавливает пользовательские заголовки в запросе (например, запрос использует заголовок, такой как X-PINGOTHER)

Когда исходным запросом является Get без пользовательских заголовков, браузер не должен делать запрос Options, который он делает сейчас. Проблема в том, что он генерирует заголовок X-Requested-With, который форсирует запрос параметров. См. Https://github.com/angular/angular.js/pull/1454 как удалить этот заголовок.



10

Если вы используете сервер nodeJS, вы можете использовать эту библиотеку, у меня она работала нормально https://github.com/expressjs/cors

var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

и после того, как вы можете сделать npm update.


4

Вот как я исправил эту проблему в ASP.NET

  • Во-первых, вы должны добавить пакет nuget Microsoft.AspNet.WebApi.Cors

  • Затем измените файл App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Добавьте этот атрибут в свой класс контроллера

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Я был в состоянии отправить JSON к действию таким образом

    $http({
            method: 'POST',
            data: JSON.stringify(data),
            url: 'actionurl',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8'
            }
        }).then(...)

Ссылка: Включение перекрестных запросов в ASP.NET Web API 2


2

Каким-то образом я исправил это, изменив

<add name="Access-Control-Allow-Headers" 
     value="Origin, X-Requested-With, Content-Type, Accept, Authorization" 
     />

в

<add name="Access-Control-Allow-Headers" 
     value="Origin, Content-Type, Accept, Authorization" 
     />

0

Отлично описано в комментарии пкозловского. У меня было рабочее решение с AngularJS 1.2.6 и ASP.NET Web Api, но когда я обновил AngularJS до 1.3.3, запросы не выполнялись.

  • Решение для сервера Web Api заключалось в том, чтобы добавить обработку запросов OPTIONS в начале метода настройки (подробнее в этом посте ):

    app.Use(async (context, next) =>
    {
        IOwinRequest req = context.Request;
        IOwinResponse res = context.Response;
        if (req.Path.StartsWithSegments(new PathString("/Token")))
        {
            var origin = req.Headers.Get("Origin");
            if (!string.IsNullOrEmpty(origin))
            {
                res.Headers.Set("Access-Control-Allow-Origin", origin);
            }
            if (req.Method == "OPTIONS")
            {
                res.StatusCode = 200;
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Methods", "GET", "POST");
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Headers", "authorization", "content-type");
                return;
            }
        }
        await next();
    });

Выше не работает для меня. Существует намного более простое решение для включения CORS для использования с AngularJS с ASP.NET WEB API 2.2 и более поздними версиями. Получите пакет Microsoft WebAPI CORS от Nuget, затем в своем файле конфигурации WebAPI ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (CORS); Подробности на сайте Microsoft здесь asp.net/web-api/overview/security/…
kmcnamee

0

Если вы используете Джерси для REST API, вы можете сделать, как показано ниже

Вам не нужно менять реализацию веб-сервисов.

Я объясню для Джерси 2.x

1) Сначала добавьте ResponseFilter, как показано ниже

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  }
}

2) затем в файле web.xml в объявлении сервлета джерси добавьте

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>

0

Я бросил пытаться решить эту проблему.

В моем IIS web.config имелось соответствующее « Access-Control-Allow-Methods», я экспериментировал с добавлением параметров конфигурации в мой код Angular, но после нескольких часов попыток заставить Chrome вызывать междоменный веб-сервис JSON, я с треском отказался.

В конце концов, я добавил тупую веб-страницу обработчика ASP.Net, получил ее , чтобы вызвать мой веб-сервис JSON и вернуть результаты. Все было готово через 2 минуты.

Вот код, который я использовал:

public class LoadJSONData : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        string URL = "......";

        using (var client = new HttpClient())
        {
            // New code:
            client.BaseAddress = new Uri(URL);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("Authorization", "Basic AUTHORIZATION_STRING");

            HttpResponseMessage response = client.GetAsync(URL).Result;
            if (response.IsSuccessStatusCode)
            {
                var content = response.Content.ReadAsStringAsync().Result;
                context.Response.Write("Success: " + content);
            }
            else
            {
                context.Response.Write(response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

И в моем угловом контроллере ...

$http.get("/Handlers/LoadJSONData.ashx")
   .success(function (data) {
      ....
   });

Я уверен, что есть более простой / более общий способ сделать это, но жизнь слишком коротка ...

Это сработало для меня, и теперь я могу заниматься нормальной работой !!


0

Для проекта IIS MVC 5 / Angular CLI (да, я хорошо знаю, что ваша проблема связана с Angular JS) с API, я сделал следующее:

web.config под <system.webServer>узлом

    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, atv2" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
    </httpProtocol>

Global.asax.cs

protected void Application_BeginRequest() {
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") {
    Response.Flush();
    Response.End();
  }
}

Это должно исправить ваши проблемы как для MVC, так и для WebAPI без необходимости делать все остальные обходы. Затем я создал HttpInterceptor в проекте Angular CLI, который автоматически добавил в соответствующую информацию заголовка. Надеюсь, это поможет кому-то в подобной ситуации.


0

Немного опоздал на вечеринку,

Если вы используете Angular 7 (или 5/6/7) и PHP в качестве API и по-прежнему получаете эту ошибку, попробуйте добавить следующие параметры заголовка в конечную точку (PHP API).

 header("Access-Control-Allow-Origin: *");
 header("Access-Control-Allow-Methods: PUT, GET, POST, PUT, OPTIONS, DELETE, PATCH");
 header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization");

Примечание : Что требуется только это Access-Control-Allow-Methods. Но я вставлю здесь два других, Access-Control-Allow-Originи Access-Control-Allow-Headersпросто потому, что вам нужно, чтобы все они были правильно установлены, чтобы Angular App правильно взаимодействовал с вашим API.

Надеюсь, это кому-нибудь поможет.

Приветствия.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.