AngularJS effectue une requête HTTP OPTIONS pour une ressource d'origine croisée

264

J'essaie de configurer AngularJS pour communiquer avec une ressource d'origine croisée où l'hôte d'actif qui fournit mes fichiers de modèle se trouve sur un domaine différent et, par conséquent, la demande XHR que Angular effectue doit être inter-domaine. J'ai ajouté l'en-tête CORS approprié à mon serveur pour la demande HTTP pour que cela fonctionne, mais cela ne semble pas fonctionner. Le problème est que lorsque j'inspecte les requêtes HTTP dans mon navigateur (chrome), la requête envoyée au fichier d'actif est une requête OPTIONS (il doit s'agir d'une requête GET).

Je ne sais pas s'il s'agit d'un bogue dans AngularJS ou si j'ai besoin de configurer quelque chose. D'après ce que je comprends, l'encapsuleur XHR ne peut pas faire de requête HTTP OPTIONS, il semble donc que le navigateur essaie de déterminer s'il est "autorisé" à télécharger l'actif en premier avant d'exécuter la demande GET. Si tel est le cas, dois-je également définir l'en-tête CORS (Access-Control-Allow-Origin: http://asset.host ... ) Avec l'hôte d'actif?

matsko
la source

Réponses:

226

Les requêtes OPTIONS ne sont en aucun cas un bogue AngularJS, c'est ainsi que la norme Cross-Origin Resource Sharing oblige les navigateurs à se comporter. Veuillez vous référer à ce document: https://developer.mozilla.org/en-US/docs/HTTP_access_control , où dans la section "Présentation", il est dit:

La norme de partage de ressources entre origines fonctionne en ajoutant de nouveaux en-têtes HTTP qui permettent aux serveurs de décrire l'ensemble des origines autorisées à lire ces informations à l'aide d'un navigateur Web. En outre, pour les méthodes de requête HTTP qui peuvent provoquer des effets secondaires sur les données utilisateur (en particulier; pour les méthodes HTTP autres que GET, ou pour l'utilisation POST avec certains types MIME). La spécification oblige les navigateurs à "contrôler en amont" la demande, en sollicitant les méthodes prises en charge auprès du serveur avec un en-tête de demande HTTP OPTIONS, puis, sur "approbation" du serveur, en envoyant la demande réelle avec la méthode de demande HTTP réelle. Les serveurs peuvent également informer les clients si des "informations d'identification" (y compris les cookies et les données d'authentification HTTP) doivent être envoyées avec les demandes.

Il est très difficile de fournir une solution générique qui fonctionnerait pour tous les serveurs WWW car la configuration variera en fonction du serveur lui-même et des verbes HTTP que vous avez l'intention de prendre en charge. Je vous encourage à parcourir cet excellent article ( http://www.html5rocks.com/en/tutorials/cors/ ) qui contient beaucoup plus de détails sur les en-têtes exacts qui doivent être envoyés par un serveur.

pkozlowski.opensource
la source
23
@matsko Pouvez-vous expliquer ce que vous avez fait pour résoudre ce problème? Je suis confronté au même problème selon lequel une demande $resource POST AngularJS génère une demande OPTIONS vers mon serveur ExpressJS backend (sur le même hôte, mais un port différent).
dbau
6
Pour tous les votants - il est presque impossible de fournir une configuration exacte pour tous les serveurs Web - la réponse prendrait 10 pages dans ce cas. Au lieu de cela, j'ai lié à un article qui fournit plus de détails.
pkozlowski.opensource
5
Vous avez raison de ne pas pouvoir prescrire de réponse - vous devrez ajouter des en-têtes à votre réponse OPTIONS qui couvre tous les en-têtes que le navigateur demande, dans mon cas, en utilisant Chrome, ce sont les en-têtes «accepter» et «x -requis-avec '. Dans Chrome, j'ai compris ce que je devais ajouter en regardant la demande réseau et en voyant ce que Chrome demandait. J'utilise nodejs / expressjs comme support, j'ai donc créé un service qui a renvoyé une réponse à la demande OPTIONS qui couvre tous les en-têtes requis. -1 parce que je ne pouvais pas utiliser cette réponse pour comprendre quoi faire, je devais le comprendre moi-même.
Ed Sykes
2
Je sais que c'est plus de 2 ans plus tard, mais ... L'OP fait plusieurs fois référence aux requêtes GET (c'est moi qui souligne): «[...] la requête envoyée au fichier d'actif est une requête OPTIONS ( ce doit être une GET demande ). » et «le navigateur essaie de déterminer s'il est« autorisé »à télécharger l'actif avant d'exécuter la demande GET ». Comment peut-il ne pas s'agir d'un bogue AngularJS? Les demandes de contrôle en amont ne devraient-elles pas être interdites pour GET?
polettix
4
@polettix, même une demande GET peut déclencher une demande de pré-vol dans un navigateur si des en-têtes personnalisés sont utilisés. Cochez «requêtes pas si simples» dans l'article que j'ai lié: html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Encore une fois, c'est le mécanisme du navigateur , pas AngularJS.
pkozlowski.opensource
70

Pour Angular 1.2.0rc1 +, vous devez ajouter une ressourceUrlWhitelist.

1.2: version de sortie, ils ont ajouté une fonction escapeForRegexp afin que vous n'ayez plus à échapper aux chaînes. Vous pouvez simplement ajouter l'URL directement

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

assurez-vous d'ajouter ** pour les sous-dossiers. Voici un jsbin fonctionnel pour 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Si vous êtes toujours sur une version rc, le Angular 1.2.0rc1 ressemble à la solution:

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

Voici un exemple jsbin où cela fonctionne pour 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pré 1.2: Pour les versions plus anciennes (ref http://better-inter.net/enabling-cors-in-angular-js/ ) vous devez ajouter les 2 lignes suivantes à votre configuration:

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

Voici un exemple jsbin où cela fonctionne pour les versions antérieures à 1.2: http://jsbin.com/olavok/11/edit

JStark
la source
Cela a fonctionné pour moi. Voici la magie appropriée pour le côté serveur avec nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Cela ne fonctionne que pour la demande GET, je ne trouve toujours pas de solution pour la demande POST sur un domaine croisé.
Pnct
2
La combinaison de cette réponse avec la réponse user2304582 ci-dessus devrait fonctionner pour les demandes POST. Vous devez dire à votre serveur d'accepter les demandes POST du serveur externe qui l'a envoyé
Charlie Martin
Cela ne semble pas fonctionner pour moi tout seul ... donc un -1. Vous avez besoin de quelque chose sur la même URL qui répondra à une demande OPTIONS dans le cadre d'une vérification pré-vol. Pouvez-vous expliquer pourquoi cela fonctionnerait?
Ed Sykes
1
@EdSykes, j'ai mis à jour la réponse pour 1.2 et ajouté un exemple de jsbin fonctionnel. J'espère que cela devrait le résoudre pour vous. Assurez-vous d'appuyer sur le bouton "Exécuter avec JS".
JStark
62

REMARQUE: je ne suis pas sûr que cela fonctionne avec la dernière version d'Angular.

ORIGINAL:

Il est également possible de remplacer la demande OPTIONS (n'a été testé que dans 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 = {};
}]);
user2899845
la source
1
Fonctionne bien pour moi avec Chrome, FF et IE 10. IE 9 et les versions antérieures doivent être testées nativement sur les machines Windows 7 ou XP.
DOM
3
Alternativement, on peut définir ceci sur la méthode de cette ressource $ seule et non globale:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r
3
Y a-t-il un problème avec cela? Cela semble si simple, et pourtant la réponse est si profonde dans le fil? modifier: n'a pas fonctionné du tout.
Sebastialonso
Où va ce code? dans app.js, controller.js ou où exactement.
Murlidhar Fichadia
Ce code ne fait rien pour une requête get. J'ai ajouté une règle distincte pour obtenir également dans ma configuration
kushalvm
34

Votre service doit répondre à une OPTIONSdemande avec des en-têtes comme ceux-ci:

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]

Voici un bon doc: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

user2304582
la source
1
Pour élaborer sur la [la même partie ACCÈS-CONTRÔLE-DEMANDE-EN-TÊTES de la demande], comme je l'ai mentionné dans une autre réponse, vous devez consulter la demande OPTIONS que votre navigateur ajoute. Ajoutez ensuite ces en-têtes au service que vous créez (ou au serveur Web). Dans expressjs qui ressemblait à: ejs.options (' ', fonction (demande, réponse) {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-demandé-with '); response.send (); });
Ed Sykes
1
Le commentaire d'Ed Sykes est très précis, sachez que les en-têtes envoyés sur la réponse OPTIONS et ceux envoyés sur la réponse POST doivent être exactement les mêmes, par exemple: Access-Control-Allow-Origin: * n'est pas identique à Access- Control-Allow-Origin: * à cause des espaces.
Lucia
20

Le même document dit

Contrairement aux demandes simples (décrites ci-dessus), les demandes "de contrôle en amont" envoient d'abord un en-tête de demande HTTP OPTIONS à la ressource de l'autre domaine, afin de déterminer si la demande réelle peut être envoyée en toute sécurité. Les demandes intersites sont contrôlées en amont comme ceci car elles peuvent avoir des implications sur les données des utilisateurs. En particulier, une demande est contrôlée en amont si:

Il utilise des méthodes autres que GET ou POST. En outre, si POST est utilisé pour envoyer des données de demande avec un type de contenu autre que application / x-www-form-urlencoded, multipart / form-data ou text / plain, par exemple si la demande POST envoie une charge utile XML au serveur en utilisant application / xml ou text / xml, la demande est alors contrôlée en amont.

Il définit des en-têtes personnalisés dans la demande (par exemple, la demande utilise un en-tête tel que X-PINGOTHER)

Lorsque la demande d'origine est Get sans en-têtes personnalisés, le navigateur ne doit pas faire de demande d'options comme il le fait maintenant. Le problème est qu'il génère un en-tête X-Requested-With qui force la requête Options. Voir https://github.com/angular/angular.js/pull/1454 sur la façon de supprimer cet en-tête

Grum Ketema
la source
10

Cela a résolu mon problème:

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

Si vous utilisez un serveur nodeJS, vous pouvez utiliser cette bibliothèque, cela a bien fonctionné pour moi https://github.com/expressjs/cors

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

app.use(cors());

et après vous pouvez faire un npm update.

Zakaria.dem
la source
4

Voici comment j'ai résolu ce problème sur ASP.NET

  • Tout d'abord, vous devez ajouter le package de nuget Microsoft.AspNet.WebApi.Cors

  • Modifiez ensuite le fichier App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Ajoutez cet attribut à votre classe de contrôleur

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • J'ai pu envoyer json à l'action de cette façon

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

Référence: activation des requêtes d'origine croisée dans l'API Web ASP.NET 2

asfez
la source
2

D'une certaine manière, je l'ai corrigé en changeant

<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" 
     />
Ved Prakash
la source
0

Parfaitement décrit dans le commentaire de pkozlowski. J'avais une solution de travail avec AngularJS 1.2.6 et ASP.NET Web Api mais quand j'ai mis à niveau AngularJS vers 1.3.3, les demandes ont échoué.

  • La solution pour le serveur Web Api consistait à ajouter la gestion des requêtes OPTIONS au début de la méthode de configuration (plus d'informations dans cet article de blog ):

    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
la source
Ce qui précède ne fonctionne pas pour moi. Il existe une solution beaucoup plus simple pour activer CORS pour une utilisation avec AngularJS avec ASP.NET WEB API 2.2 et versions ultérieures. Obtenez le package Microsoft WebAPI CORS auprès de Nuget puis dans votre fichier de configuration WebAPI ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (cors); Les détails sont sur le site de Microsoft ici asp.net/web-api/overview/security/…
kmcnamee
0

Si vous utilisez Jersey pour les API REST, vous pouvez procéder comme ci-dessous

Vous n'avez pas à modifier votre implémentation de services Web.

Je vais vous expliquer pour Jersey 2.x

1) Ajoutez d'abord un ResponseFilter comme indiqué ci-dessous

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) puis dans le web.xml, dans la déclaration du jersey servlet, ajoutez ce qui suit

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

J'ai renoncé à essayer de résoudre ce problème.

Mon web.config IIS avait le "Access-Control-Allow-Methods " , j'ai expérimenté l'ajout de paramètres de configuration à mon code angulaire, mais après avoir brûlé quelques heures en essayant de faire appeler Chrome un service Web JSON interdomaine, j'ai abandonné misérablement.

À la fin, j'ai ajouté une page Web de gestionnaire ASP.Net stupide, cela pour appeler mon service Web JSON et renvoyer les résultats. Il était opérationnel en 2 minutes.

Voici le code que j'ai utilisé:

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

Et dans mon contrôleur angulaire ...

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

Je suis sûr qu'il existe un moyen plus simple / plus générique de le faire, mais la vie est trop courte ...

Cela a fonctionné pour moi et je peux maintenant faire un travail normal !!

Mike Gledhill
la source
0

Pour un projet IIS MVC 5 / Angular CLI (Oui, je suis bien conscient que votre problème est lié à Angular JS) avec l'API, j'ai fait ce qui suit:

web.config sous le <system.webServer>noeud

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

Cela devrait résoudre vos problèmes pour MVC et WebAPI sans avoir à faire tout le reste. J'ai ensuite créé un HttpInterceptor dans le projet CLI angulaire qui a automatiquement ajouté les informations d'en-tête pertinentes. J'espère que cela aide quelqu'un dans une situation similaire.

Damon Drake
la source
0

Un peu tard pour la fête,

Si vous utilisez Angular 7 (ou 5/6/7) et PHP comme API et que vous obtenez toujours cette erreur, essayez d'ajouter les options d'en-tête suivantes au point final (API PHP).

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

Remarque : ce qui nécessite uniquement, c'est Access-Control-Allow-Methods. Mais, je colle ici deux autres Access-Control-Allow-Originet Access-Control-Allow-Headers, simplement parce que vous aurez besoin de tout cela pour être correctement configuré afin que Angular App puisse parler correctement à votre API.

J'espère que cela aide quelqu'un.

À votre santé.

Anjana Silva
la source