AngularJS führt eine OPTIONS-HTTP-Anforderung für eine Cross-Origin-Ressource aus

264

Ich versuche, AngularJS für die Kommunikation mit einer Cross-Origin-Ressource einzurichten, bei der sich der Asset-Host, der meine Vorlagendateien bereitstellt, in einer anderen Domäne befindet. Daher muss die von Angular ausgeführte XHR-Anforderung domänenübergreifend sein. Ich habe meinem Server den entsprechenden CORS-Header für die HTTP-Anforderung hinzugefügt, damit dies funktioniert, aber es scheint nicht zu funktionieren. Das Problem ist, dass beim Überprüfen der HTTP-Anforderungen in meinem Browser (Chrome) die an die Asset-Datei gesendete Anforderung eine OPTIONS-Anforderung ist (es sollte sich um eine GET-Anforderung handeln).

Ich bin nicht sicher, ob dies ein Fehler in AngularJS ist oder ob ich etwas konfigurieren muss. Soweit ich weiß, kann der XHR-Wrapper keine OPTIONS-HTTP-Anforderung stellen. Es sieht also so aus, als würde der Browser versuchen, herauszufinden, ob das Asset zuerst heruntergeladen werden darf, bevor er die GET-Anforderung ausführt. Wenn dies der Fall ist, muss ich dann den CORS-Header (Access-Control-Allow-Origin: http://asset.host ... ) auch mit dem Asset-Host festlegen ?

Matsko
quelle

Antworten:

226

OPTIONS-Anforderungen sind keineswegs ein AngularJS-Fehler. Auf diese Weise fordert der Cross-Origin Resource Sharing-Standard das Verhalten von Browsern. Weitere Informationen finden Sie in diesem Dokument: https://developer.mozilla.org/en-US/docs/HTTP_access_control . Im Abschnitt "Übersicht" heißt es:

Der Cross-Origin Resource Sharing-Standard fügt neue HTTP-Header hinzu, mit denen Server die Ursprünge beschreiben können, die diese Informationen mit einem Webbrowser lesen dürfen. Darüber hinaus für HTTP-Anforderungsmethoden, die Nebenwirkungen auf Benutzerdaten verursachen können (insbesondere für andere HTTP-Methoden als GET oder für die POST-Verwendung mit bestimmten MIME-Typen). Die Spezifikation schreibt vor, dass Browser die Anforderung "vorab prüfen", unterstützte Methoden vom Server mit einem HTTP OPTIONS-Anforderungsheader anfordern und dann nach "Genehmigung" vom Server die tatsächliche Anforderung mit der tatsächlichen HTTP-Anforderungsmethode senden. Server können Clients auch benachrichtigen, ob "Anmeldeinformationen" (einschließlich Cookies und HTTP-Authentifizierungsdaten) mit Anforderungen gesendet werden sollen.

Es ist sehr schwierig, eine generische Lösung bereitzustellen, die für alle WWW-Server funktioniert, da das Setup abhängig vom Server selbst und den HTTP-Verben, die Sie unterstützen möchten, unterschiedlich ist. Ich möchte Sie ermutigen, diesen hervorragenden Artikel ( http://www.html5rocks.com/en/tutorials/cors/ ) zu lesen , der viel mehr Details zu den genauen Headern enthält, die von einem Server gesendet werden müssen.

pkozlowski.opensource
quelle
23
@matsko Kannst du näher erläutern, was du getan hast, um dieses Problem zu lösen? Ich habe das gleiche Problem, bei dem eine AngularJS- $resource POST- Anforderung eine OPTIONS- Anforderung an meinen Backend-ExpressJS-Server generiert (auf demselben Host, aber an einem anderen Port).
Bau
6
Für alle Abwärtswähler - es ist nahezu unmöglich, eine genaue Konfiguration für alle Webserver da draußen bereitzustellen - würde die Antwort in diesem Fall 10 Seiten dauern. Stattdessen habe ich auf einen Artikel verlinkt, der weitere Details enthält.
pkozlowski.opensource
5
Sie haben Recht, dass Sie keine Antwort vorschreiben können - Sie müssen Ihrer OPTIONS-Antwort Header hinzufügen, die alle Header abdecken, die der Browser anfordert. In meinem Fall waren es in Chrome die Header 'accept' und 'x -gefragt-mit '. In Chrome habe ich herausgefunden, was ich hinzufügen muss, indem ich mir die Netzwerkanforderung angesehen und festgestellt habe, wonach Chrome gefragt hat. Ich verwende nodejs / expressjs als Backup, daher habe ich einen Dienst erstellt, der eine Antwort auf die OPTIONS-Anforderung zurückgibt, die alle erforderlichen Header abdeckt. -1 weil ich diese Antwort nicht verwenden konnte, um herauszufinden, was zu tun ist, musste ich es selbst herausfinden.
Ed Sykes
2
Ich weiß, dass es 2+ Jahre später ist, aber ... Das OP bezieht sich mehrmals auf GET-Anfragen (Hervorhebung von mir hinzugefügt): «[...] Die an die Asset-Datei gesendete Anfrage ist eine OPTIONS-Anfrage ( es sollte eine GET sein Anfrage ). » und «der Browser versucht herauszufinden, ob das Asset zuerst" heruntergeladen "werden darf, bevor die GET-Anforderung ausgeführt wird ». Wie kann es dann kein AngularJS-Fehler sein? Sollten Preflight-Anfragen für GET nicht verboten werden?
Polettix
4
@polettix Selbst eine GET-Anforderung kann eine Vorfluganforderung in einem Browser auslösen, wenn benutzerdefinierte Header verwendet werden. Überprüfen Sie "nicht so einfache Anfragen" in dem Artikel, den ich verlinkt habe: html5rocks.com/de/tutorials/cors/#toc-making-a-cors-request . Wieder ist es der Mechanismus des Browsers , nicht AngularJS.
pkozlowski.opensource
70

Für Angular 1.2.0rc1 + müssen Sie eine resourceUrlWhitelist hinzufügen.

1.2: Release-Version Sie haben eine EscapeForRegexp-Funktion hinzugefügt, damit Sie nicht länger den Strings entkommen müssen. Sie können die URL einfach direkt hinzufügen

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

Stellen Sie sicher, dass Sie ** für Unterordner hinzufügen. Hier ist ein funktionierender jsbin für 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Wenn Sie noch eine RC-Version haben, sieht die Lösung Angular 1.2.0rc1 folgendermaßen aus:

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

Hier ist ein jsbin-Beispiel, in dem es für 1.2.0rc1 funktioniert: http://jsbin.com/olavok/144/edit


Pre 1.2: Für ältere Versionen (siehe http://better-inter.net/enabling-cors-in-angular-js/ ) müssen Sie Ihrer Konfiguration die folgenden 2 Zeilen hinzufügen:

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

Hier ist ein jsbin-Beispiel, in dem es für Versionen vor 1.2 funktioniert: http://jsbin.com/olavok/11/edit

JStark
quelle
Das hat bei mir funktioniert. Hier ist die passende Magie für die Serverseite mit nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Dies funktioniert nur für GET-Anforderungen. Es kann immer noch keine Lösung für POST-Anforderungen für domänenübergreifende Anforderungen gefunden werden.
Pnct
2
Das Kombinieren dieser Antwort mit der obigen Antwort von user2304582 sollte für POST-Anforderungen funktionieren. Sie müssen Ihren Server anweisen, POST-Anfragen von dem externen Server zu akzeptieren, der ihn gesendet hat
Charlie Martin
Das sieht für mich nicht so aus, als würde es von alleine funktionieren ... also ein -1. Sie benötigen etwas unter derselben URL, das im Rahmen einer Überprüfung vor dem Flug auf eine OPTIONS-Anfrage reagiert. Können Sie erklären, warum dies funktionieren würde?
Ed Sykes
1
@EdSykes, ich habe die Antwort für 1.2 aktualisiert und ein funktionierendes jsbin-Beispiel hinzugefügt. Hoffentlich sollte das für Sie gelöst werden. Stellen Sie sicher, dass Sie auf die Schaltfläche "Mit JS ausführen" klicken.
JStark
62

HINWEIS: Nicht sicher, ob es mit der neuesten Version von Angular funktioniert.

ORIGINAL:

Es ist auch möglich, die OPTIONS-Anforderung zu überschreiben (wurde nur in Chrome getestet):

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 = {};
}]);
user2899845
quelle
1
Funktioniert gut mit Chrome, FF und IE 10. IE 9 und niedriger sollte nativ auf Windows 7- oder XP-Computern getestet werden.
DOM
3
Alternativ kann man dies auf die Methode dieser $ Ressource allein und nicht global $resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
einstellen
3
Gibt es einen Haken dabei? Es scheint so einfach und doch ist die Antwort so tief im Thread? edit: Hat überhaupt nicht funktioniert.
Sebastialonso
Wohin geht dieser Code? in app.js, controller.js oder wo genau.
Murlidhar Fichadia
Dieser Code tut nichts für eine Get-Anfrage. Ich habe eine separate Regel hinzugefügt, um auch in meiner Konfiguration zu erhalten
kushalvm
34

Ihr Dienst muss eine OPTIONSAnfrage mit folgenden Überschriften beantworten :

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]

Hier ist ein gutes Dokument: http://www.html5rocks.com/de/tutorials/cors/#toc-adding-cors-support-to-the-server

user2304582
quelle
1
Um auf den Teil [die gleichen ACCESS-CONTROL-REQUEST-HEADERS von Anfrage] einzugehen, wie ich in einer anderen Antwort erwähnt habe, müssen Sie sich die OPTIONS-Anfrage ansehen, die Ihr Browser hinzufügt. Fügen Sie dann diese Header zu dem Dienst hinzu, den Sie erstellen (oder zu dem Webserver). In Expressjs, die wie folgt aussahen: ejs.options (' ', Funktion (Anfrage, Antwort) {response.header ('Access-Control-Allow-Origin', ' '); response.header ('Access-Control-Allow-Methods ',' GET, PUT, POST, DELETE '); response.header (' Access-Control-Allow-Headers ',' Inhaltstyp, Autorisierung, Akzeptieren, x-angefordert-mit '); response.send (); });
Ed Sykes
1
Der Kommentar von Ed Sykes ist sehr genau. Beachten Sie, dass die in der OPTIONS-Antwort und in der POST-Antwort gesendeten Header genau identisch sein sollten, z. B.: Access-Control-Allow-Origin: * ist nicht identisch mit Access- Control-Allow-Origin: * wegen der Leerzeichen.
Lucia
20

Das gleiche Dokument sagt

Im Gegensatz zu einfachen Anforderungen (siehe oben) senden "Preflighted" -Anforderungen zuerst einen HTTP OPTIONS-Anforderungsheader an die Ressource in der anderen Domäne, um festzustellen, ob die tatsächliche Anforderung sicher zu senden ist. Standortübergreifende Anforderungen werden auf diese Weise vorab ausgeführt, da sie Auswirkungen auf Benutzerdaten haben können. Insbesondere wird eine Anfrage vorab geflogen, wenn:

Es werden andere Methoden als GET oder POST verwendet. Wenn POST zum Senden von Anforderungsdaten mit einem anderen Inhaltstyp als application / x-www-form-urlencoded, multipart / form-data oder text / plain verwendet wird, z. B. wenn die POST-Anforderung eine XML-Nutzlast an den Server sendet Wenn Sie application / xml oder text / xml verwenden, wird die Anforderung vorab ausgeführt.

Es werden benutzerdefinierte Header in der Anforderung festgelegt (z. B. verwendet die Anforderung einen Header wie X-PINGOTHER).

Wenn die ursprüngliche Anforderung Get ohne benutzerdefinierte Header lautet, sollte der Browser keine Optionsanforderung stellen, wie dies jetzt der Fall ist. Das Problem ist, dass ein Header X-Requested-With generiert wird, der die Optionsanforderung erzwingt. Informationen zum Entfernen dieses Headers finden Sie unter https://github.com/angular/angular.js/pull/1454

Grum Ketema
quelle
10

Dies hat mein Problem behoben:

$http.defaults.headers.post["Content-Type"] = "text/plain";
Cem Arguvanlı
quelle
10

Wenn Sie einen NodeJS-Server verwenden, können Sie diese Bibliothek verwenden. Für mich hat dies problemlos funktioniert: https://github.com/expressjs/cors

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

app.use(cors());

und nachdem du ein machen kannst npm update.

Zakaria.dem
quelle
4

Hier ist die Art und Weise, wie ich dieses Problem in ASP.NET behoben habe

  • Zunächst sollten Sie das Nuget-Paket Microsoft.AspNet.WebApi.Cors hinzufügen

  • Ändern Sie dann die Datei App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Fügen Sie dieses Attribut Ihrer Controller-Klasse hinzu

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Auf diese Weise konnte ich json zur Aktion schicken

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

Referenz: Aktivieren von Cross-Origin-Anforderungen in ASP.NET Web API 2

asfez
quelle
2

Irgendwie habe ich es durch Ändern behoben

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

zu

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

Perfekt beschrieben in pkozlowskis Kommentar. Ich hatte eine funktionierende Lösung mit AngularJS 1.2.6 und ASP.NET Web Api, aber als ich AngularJS auf 1.3.3 aktualisiert hatte, schlugen Anforderungen fehl.

  • Die Lösung für den Web-API-Server bestand darin, die Behandlung der OPTIONS-Anforderungen zu Beginn der Konfigurationsmethode hinzuzufügen (weitere Informationen in diesem Blog-Beitrag ):

    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
quelle
Das obige funktioniert bei mir nicht. Es gibt eine viel einfachere Lösung, um CORS für die Verwendung mit AngularJS mit ASP.NET WEB API 2.2 und höher zu aktivieren. Holen Sie sich das Microsoft WebAPI CORS-Paket von Nuget und dann in Ihre WebAPI-Konfigurationsdatei ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (cors); Details finden Sie auf der Microsoft-Website hier asp.net/web-api/overview/security/…
kmcnamee
0

Wenn Sie Jersey für REST-APIs verwenden, können Sie wie folgt vorgehen

Sie müssen Ihre Webservices-Implementierung nicht ändern.

Ich werde für Jersey 2.x erklären

1) Fügen Sie zuerst einen ResponseFilter hinzu, wie unten gezeigt

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) Fügen Sie dann in der Datei web.xml in der Trikot-Servlet-Deklaration Folgendes hinzu

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

Ich habe es aufgegeben, dieses Problem zu beheben.

Meine IIS web.config enthielt das relevante " Access-Control-Allow-Methods". Ich habe versucht, meinem Angular-Code Konfigurationseinstellungen hinzuzufügen, aber nachdem ich einige Stunden gebrannt hatte, um Chrome dazu zu bringen, einen domänenübergreifenden JSON-Webdienst aufzurufen, gab ich kläglich auf.

Am Ende habe ich eine blöde ASP.Net-Handler-Webseite hinzugefügt, diese dazu gebracht, meinen JSON-Webdienst aufzurufen und die Ergebnisse zurückzugeben. Es war in 2 Minuten betriebsbereit.

Hier ist der Code, den ich verwendet habe:

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

Und in meinem Angular Controller ...

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

Ich bin sicher, es gibt einen einfacheren / allgemeineren Weg, dies zu tun, aber das Leben ist zu kurz ...

Das hat bei mir funktioniert und ich kann jetzt mit der normalen Arbeit weitermachen !!

Mike Gledhill
quelle
0

Für ein IIS MVC 5 / Angular CLI-Projekt (Ja, ich bin mir bewusst, dass Ihr Problem bei Angular JS liegt) mit API habe ich Folgendes getan:

web.config unter <system.webServer>Knoten

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

Das sollte Ihre Probleme sowohl für MVC als auch für WebAPI beheben, ohne dass alle anderen herumlaufen müssen. Ich habe dann einen HttpInterceptor im Angular CLI-Projekt erstellt, der automatisch die entsprechenden Header-Informationen hinzufügte. Hoffe das hilft jemandem in einer ähnlichen Situation.

Damon Drake
quelle
0

Wenig spät zur Party,

Wenn Sie Angular 7 (oder 5/6/7) und PHP als API verwenden und dieser Fehler weiterhin angezeigt wird, versuchen Sie, dem Endpunkt (PHP-API) die folgenden Header-Optionen hinzuzufügen.

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

Hinweis : Was nur benötigt wird, ist Access-Control-Allow-Methods. Aber ich füge hier zwei weitere ein Access-Control-Allow-Originund Access-Control-Allow-Headerseinfach, weil Sie alle diese Einstellungen richtig vornehmen müssen, damit Angular App ordnungsgemäß mit Ihrer API kommunizieren kann.

Hoffe das hilft jemandem.

Prost.

Anjana Silva
quelle