AngularJS wykonuje OPCJE żądanie HTTP dla zasobu cross-origin

264

Usiłuję skonfigurować AngularJS do komunikowania się z zasobem pochodzącym z różnych źródeł, gdzie host zasobów, który dostarcza moje pliki szablonów, znajduje się w innej domenie, a zatem żądanie XHR, które wykonuje angular, musi być między domenami. Dodałem odpowiedni nagłówek CORS do mojego serwera dla żądania HTTP, aby to działało, ale wydaje się, że nie działa. Problem polega na tym, że kiedy sprawdzam żądania HTTP w mojej przeglądarce (chrome), żądanie wysłane do pliku zasobu jest żądaniem OPTIONS (powinno to być żądanie GET).

Nie jestem pewien, czy jest to błąd w AngularJS, czy też muszę coś skonfigurować. Z tego, co rozumiem, opakowanie XHR nie może wykonać OPCJI żądania HTTP, więc wygląda na to, że przeglądarka próbuje ustalić, czy „najpierw” można pobrać zasób, zanim wykona żądanie GET. Jeśli tak jest, to czy muszę ustawić nagłówek CORS (Access-Control-Allow-Origin: http://asset.host ... ) Również z hostem zasobów?

matsko
źródło

Odpowiedzi:

226

Żądanie OPCJE w żadnym wypadku nie jest błędem AngularJS, w ten sposób standardowe udostępnianie zasobów między źródłami nakazuje przeglądarkom zachowanie. Proszę zapoznać się z tym dokumentem: https://developer.mozilla.org/en-US/docs/HTTP_access_control , gdzie w sekcji „Przegląd” mówi:

Standard współdzielenia zasobów między źródłami działa poprzez dodanie nowych nagłówków HTTP, które pozwalają serwerom opisać zestaw źródeł, które mogą czytać te informacje za pomocą przeglądarki internetowej. Ponadto w przypadku metod żądania HTTP, które mogą powodować skutki uboczne danych użytkownika (w szczególności; w przypadku metod HTTP innych niż GET lub w przypadku użycia POST z niektórymi typami MIME). Specyfikacja wymaga, aby przeglądarki „wstępnie sprawdzały” żądanie, żądając obsługiwanych metod z serwera z nagłówkiem żądania OPCJE HTTP, a następnie, po „zatwierdzeniu” z serwera, wysyłały rzeczywiste żądanie z rzeczywistą metodą żądania HTTP. Serwery mogą również powiadamiać klientów, czy „poświadczenia” (w tym pliki cookie i dane uwierzytelniające HTTP) powinny być wysyłane z żądaniami.

Bardzo trudno jest zapewnić ogólne rozwiązanie, które działałoby dla wszystkich serwerów WWW, ponieważ konfiguracja będzie się różnić w zależności od samego serwera i czasowników HTTP, które zamierzasz obsługiwać. Zachęcam do zapoznania się z tym doskonałym artykułem ( http://www.html5rocks.com/en/tutorials/cors/ ), który zawiera znacznie więcej szczegółów na temat dokładnych nagłówków, które muszą zostać wysłane przez serwer.

pkozlowski.opensource
źródło
23
@matsko Czy możesz wyjaśnić, co zrobiłeś, aby to rozwiązać? Mam do czynienia z tym samym problemem, w wyniku którego żądanie $resource POST AngularJS generuje żądanie OPCJE do mojego serwera ExpressJS zaplecza (na tym samym hoście, ale innym porcie).
dbau
6
Dla wszystkich osób, które negatywnie głosują - niemożliwe jest zapewnienie dokładnej konfiguracji wszystkich serwerów WWW - w tym przypadku odpowiedź zajęłaby 10 stron. Zamiast tego odsyłam do artykułu, który zawiera więcej szczegółów.
pkozlowski.opensource
5
Masz rację, że nie możesz przepisać odpowiedzi - musisz dodać nagłówki do odpowiedzi OPTIONS, która obejmuje wszystkie nagłówki, o które prosi przeglądarka, w moim przypadku przy użyciu chrome to były nagłówki „zaakceptuj” i „x -pytane-z ”. W chrome, zorientowałem się, co muszę dodać, patrząc na żądanie sieciowe i sprawdzając, o co prosił chrom. Używam nodejs / expressjs jako kopii zapasowej, więc utworzyłem usługę, która zwróciła odpowiedź na żądanie OPTIONS, które obejmuje wszystkie wymagane nagłówki. -1, ponieważ nie mogłem użyć tej odpowiedzi, aby wymyślić, co robić, musiałem sam to wymyślić.
Ed Sykes,
2
Wiem, że to ponad 2 lata później, ale ... PO odnosi się do żądań GET wiele razy (podkreślenie dodane przeze mnie): «[...] żądanie wysłane do pliku aktywów jest żądaniem OPCJE ( powinno być GET żądanie ). » a „przeglądarka próbuje ustalić, czy„ najpierw ”można pobrać zasób, zanim wykona żądanie GET ”. Jak więc może nie być to błąd AngularJS? Czy prośby o wstępny lot nie powinny być zabronione dla GET?
polettix,
4
@polettix nawet żądanie GET może wywołać żądanie przed lotem w przeglądarce, jeśli zostaną użyte niestandardowe nagłówki. Sprawdź „niezbyt proste żądania” w artykule, który podłączyłem : html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Po raz kolejny jest to mechanizm przeglądarki , a nie AngularJS.
pkozlowski.opensource
70

W przypadku Angulara 1.2.0rc1 + musisz dodać zasóbUrlWhitelist.

1.2: wersja wydania dodali funkcję escapeForRegexp, więc nie musisz już uciekać od łańcuchów. Możesz po prostu dodać adres URL bezpośrednio

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

pamiętaj, aby dodać ** do podfolderów. Oto działający jsbin dla 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Jeśli nadal jesteś w wersji rc, Angular 1.2.0rc1 rozwiązanie wygląda następująco:

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

Oto przykład jsbin, w którym działa dla 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pre 1.2: W przypadku starszych wersji (zob. Http://better-inter.net/enabling-cors-in-angular-js/ ) musisz dodać do konfiguracji następujące 2 wiersze:

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

Oto przykład jsbin, w którym działa on dla wersji wcześniejszych niż 1.2: http://jsbin.com/olavok/11/edit

JStark
źródło
To zadziałało dla mnie. Oto odpowiednia magia dla serwera po stronie nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Działa to tylko w przypadku żądania GET, nadal nie można znaleźć rozwiązania dla żądania POST w wielu domenach.
Pnct
2
Łączenie tej odpowiedzi z odpowiedzią user2304582 powyżej powinno działać w przypadku żądań POST. Musisz powiedzieć swojemu serwerowi, aby akceptował żądania POST z zewnętrznego serwera, który go wysłał
Charlie Martin
To nie wygląda na to, że zadziała samo dla mnie ... więc -1. Potrzebujesz czegoś pod tym samym adresem URL, który odpowie na żądanie OPTIONS w ramach kontroli przed lotem. Czy możesz wyjaśnić, dlaczego to zadziała?
Ed Sykes,
1
@EdSykes, zaktualizowałem odpowiedź dla wersji 1.2 i dodałem działający przykład jsbin. Mam nadzieję, że powinno to rozwiązać problem. Upewnij się, że nacisnąłeś przycisk „Uruchom z JS”.
JStark,
62

UWAGA: Nie jestem pewien, czy działa z najnowszą wersją Angular.

ORYGINALNY:

Możliwe jest również zastąpienie żądania OPCJE (przetestowane tylko w 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 = {};
}]);
użytkownik 2899845
źródło
1
Działa dobrze dla mnie z Chrome, FF i IE 10. IE 9 i nowsze wersje powinny być testowane natywnie na komputerach z systemem Windows 7 lub XP.
DOM
3
Alternatywnie można ustawić tę metodę tylko dla tego zasobu $, a nie globalnie:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r
3
Czy jest z tym jakiś haczyk? Wydaje się to takie proste, a jednak odpowiedź jest tak głęboko w wątku? edycja: W ogóle nie działał.
Sebastialonso,
Gdzie idzie ten kod? w app.js, controller.js lub gdzie dokładnie.
Murlidhar Fichadia
Ten kod nic nie robi dla żądania get. Dodałem
osobną
34

Twoja usługa musi odpowiedzieć na OPTIONSżądanie za pomocą takich nagłówków:

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]

Oto dobry dokument: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

użytkownik2304582
źródło
1
Aby rozwinąć kwestię [tej samej części NAGŁÓWKÓW KONTROLI DOSTĘPU z żądania], jak wspomniałem w innej odpowiedzi, należy spojrzeć na prośbę OPCJE, którą dodaje przeglądarka. Następnie dodaj te nagłówki do budowanej usługi (lub serwera WWW). W expressjs, który wyglądał następująco: 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-request-with ”); response.send (); });
Ed Sykes,
1
Komentarz Eda Sykesa jest bardzo dokładny, należy pamiętać, że nagłówki wysłane w odpowiedzi OPCJE i te wysłane w odpowiedzi POST powinny być dokładnie takie same, na przykład: Kontrola dostępu-Zezwalaj na pochodzenie: * to nie to samo co Access- Control-Allow-Origin: * z powodu spacji.
Lucia,
20

Ten sam dokument mówi

W przeciwieństwie do prostych żądań (omówionych powyżej), wnioski „wstępnie sprawdzone” najpierw wysyłają nagłówek żądania OPCJE HTTP do zasobu w innej domenie, w celu ustalenia, czy faktyczne żądanie jest bezpieczne do wysłania. Żądania między witrynami są wstępnie sprawdzane w ten sposób, ponieważ mogą mieć wpływ na dane użytkownika. W szczególności żądanie jest wstępnie sprawdzane, jeśli:

Wykorzystuje metody inne niż GET lub POST. Ponadto, jeśli POST jest używany do wysyłania danych żądania z typem treści innym niż application / x-www-form-urlencoded, multipart / form-data lub text / plain, np. Jeśli żądanie POST wysyła ładunek XML do serwera przy użyciu application / xml lub text / xml, żądanie jest wstępnie sprawdzane.

Ustawia niestandardowe nagłówki w żądaniu (np. Żądanie używa nagłówka, takiego jak X-PINGOTHER)

Gdy pierwotnym żądaniem jest Uzyskaj bez niestandardowych nagłówków, przeglądarka nie powinna wysyłać żądania Opcji, co robi teraz. Problem polega na tym, że generuje nagłówek X-Requested-With, który wymusza żądanie opcji. Zobacz https://github.com/angular/angular.js/pull/1454, jak usunąć ten nagłówek

Grum Ketema
źródło
10

To naprawiło mój problem:

$http.defaults.headers.post["Content-Type"] = "text/plain";
Cem Arguvanlı
źródło
10

Jeśli korzystasz z serwera nodeJS, możesz użyć tej biblioteki, działało to dobrze dla mnie https://github.com/expressjs/cors

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

app.use(cors());

i kiedy możesz zrobić npm update.

Zakaria.dem
źródło
4

Oto sposób, w jaki rozwiązałem ten problem na ASP.NET

  • Najpierw należy dodać pakiet nuget Microsoft.AspNet.WebApi.Cors

  • Następnie zmodyfikuj plik App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Dodaj ten atrybut do swojej klasy kontrolera

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • W ten sposób mogłem wysłać Jsona na akcję

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

Odwołanie: Włączanie żądań krzyżowania pochodzenia w ASP.NET Web API 2

asfez
źródło
2

Jakoś to naprawiłem, zmieniając

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

do

<add name="Access-Control-Allow-Headers" 
     value="Origin, Content-Type, Accept, Authorization" 
     />
Ved Prakash
źródło
0

Idealnie opisane w komentarzu pkozłowskiego. Miałem działające rozwiązanie z AngularJS 1.2.6 i ASP.NET Web Api, ale kiedy uaktualniłem AngularJS do 1.3.3, żądania nie powiodły się.

  • Rozwiązaniem dla serwera Web Api było dodanie obsługi żądań OPTIONS na początku metody konfiguracji (więcej informacji w tym poście na blogu ):

    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();
    });
Jiří Kavulák
źródło
Powyższe nie działa dla mnie. Istnieje o wiele prostsze rozwiązanie umożliwiające CORSowi korzystanie z AngularJS z ASP.NET WEB API 2.2 i nowszymi. Pobierz pakiet Microsoft WebAPI CORS od Nuget, a następnie w pliku konfiguracyjnym WebAPI ... var cors = new EnableCorsAttribute („www.example.com”, „ ”, „ ”); config.EnableCors (cors); Szczegóły znajdują się na stronie Microsoft tutaj asp.net/web-api/overview/security/…
kmcnamee
0

Jeśli używasz Jersey dla interfejsów API REST, możesz wykonać następujące czynności

Nie musisz zmieniać implementacji usług sieciowych.

Wyjaśnię dla Jersey 2.x

1) Najpierw dodaj filtr odpowiedzi, jak pokazano poniżej

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) następnie w pliku web.xml, w deklaracji serwletu jersey dodaj poniżej

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>
nieokreślony
źródło
0

Zrezygnowałem z próby rozwiązania tego problemu.

Mój web.config IIS miał odpowiedni „Access-Control-Allow-Methods ”, eksperymentowałem z dodawaniem ustawień konfiguracji do mojego kodu Angular, ale po spaleniu kilku godzin, próbując przekonać Chrome do wywołania usługi internetowej JSON między domenami, zrezygnowałem.

Na koniec dodałem głupią stronę obsługi ASP.Net, mam to zadzwonić do mojego serwisu WWW JSON, i zwróci wyniki. Uruchomiono go w 2 minuty.

Oto kod, którego użyłem:

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

I w moim kontrolerze Angular ...

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

Jestem pewien, że istnieje prostszy / bardziej ogólny sposób, ale życie jest za krótkie ...

To działało dla mnie i mogę teraz zacząć normalną pracę !!

Mike Gledhill
źródło
0

W przypadku projektu IIS MVC 5 / Angular CLI (Tak, dobrze wiem, że twój problem dotyczy Angular JS) z interfejsem API, wykonałem następujące czynności:

web.config w <system.webServer>węźle

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

To powinno rozwiązać problemy zarówno z MVC, jak i WebAPI bez konieczności wykonywania innych czynności. Następnie utworzyłem HttpInterceptor w projekcie Angular CLI, który automatycznie dodał odpowiednie informacje nagłówka. Mam nadzieję, że to pomoże komuś w podobnej sytuacji.

Damon Drake
źródło
0

Trochę późno na imprezę,

Jeśli używasz Angular 7 (lub 5/6/7) i PHP jako API i nadal pojawia się ten błąd, spróbuj dodać następujące opcje nagłówka do punktu końcowego (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");

Uwaga : Wymagane jest tylko Access-Control-Allow-Methods. Ale wklejam tutaj pozostałe dwaAccess-Control-Allow-Origin i Access-Control-Allow-Headerspo prostu dlatego, że będziesz musiał je wszystkie poprawnie ustawić, aby Angular App poprawnie komunikował się z twoim API.

Mam nadzieję, że to komuś pomoże.

Twoje zdrowie.

Anjana Silva
źródło