AngularJS realiza una solicitud HTTP OPTIONS para un recurso de origen cruzado

264

Estoy tratando de configurar AngularJS para comunicarse con un recurso de origen cruzado donde el host de activos que entrega mis archivos de plantilla está en un dominio diferente y, por lo tanto, la solicitud XHR de que angular realiza debe ser de dominio cruzado. Agregué el encabezado CORS apropiado a mi servidor para la solicitud HTTP para que esto funcione, pero parece que no funciona. El problema es que cuando inspecciono las solicitudes HTTP en mi navegador (Chrome), la solicitud enviada al archivo de activos es una solicitud de OPCIONES (debería ser una solicitud GET).

No estoy seguro de si esto es un error en AngularJS o si necesito configurar algo. Por lo que entiendo, el contenedor XHR no puede realizar una solicitud HTTP OPTIONS, por lo que parece que el navegador está tratando de averiguar si está "permitido" descargar el activo primero antes de realizar la solicitud GET. Si este es el caso, ¿debo configurar el encabezado CORS (Access-Control-Allow-Origin: http://asset.host ... ) con el host del activo también?

Matsko
fuente

Respuestas:

226

La solicitud de OPTIONS no es en absoluto un error de AngularJS, así es como el estándar Cross-Origin Resource Sharing exige que los navegadores se comporten. Consulte este documento: https://developer.mozilla.org/en-US/docs/HTTP_access_control , donde en la sección "Descripción general" dice:

El estándar Cross-Origin Resource Sharing funciona agregando nuevos encabezados HTTP que permiten a los servidores describir el conjunto de orígenes que pueden leer esa información usando un navegador web. Además, para los métodos de solicitud HTTP que pueden causar efectos secundarios en los datos del usuario (en particular; para métodos HTTP que no sean GET, o para el uso POST con ciertos tipos MIME). La especificación exige que los navegadores "realicen una verificación previa" de la solicitud, solicitando métodos admitidos del servidor con un encabezado de solicitud de OPCIONES HTTP y luego, tras la "aprobación" del servidor, enviando la solicitud real con el método de solicitud HTTP real. Los servidores también pueden notificar a los clientes si las "credenciales" (incluidas las cookies y los datos de autenticación HTTP) deben enviarse con solicitudes.

Es muy difícil proporcionar una solución genérica que funcione para todos los servidores WWW, ya que la configuración variará según el servidor en sí y los verbos HTTP que intente admitir. Le animo a que lea este excelente artículo ( http://www.html5rocks.com/en/tutorials/cors/ ) que tiene muchos más detalles sobre los encabezados exactos que debe enviar un servidor.

pkozlowski.opensource
fuente
23
@matsko ¿Puedes explicar qué hiciste para resolver esto? Estoy enfrentando el mismo problema por el cual una solicitud $resource POST de AngularJS está generando una solicitud de OPCIONES a mi servidor ExpressJS de fondo (en el mismo host; pero un puerto diferente).
dbau
66
Para todos los votantes que están abajo, es casi imposible proporcionar una configuración de configuración exacta para todos los servidores web, la respuesta tomaría 10 páginas en este caso. En cambio, me he vinculado a un artículo que proporciona más detalles.
pkozlowski.opensource
55
Tienes razón en que no puedes prescribir una respuesta: deberás agregar encabezados a tu respuesta de OPTIONS que cubra todos los encabezados que solicite el navegador, en mi caso, usando Chrome, fueron los encabezados 'aceptar' y 'x -solicitado-con '. En Chrome, descubrí lo que necesitaba agregar al mirar la solicitud de red y ver qué estaba pidiendo Chrome. Estoy usando nodejs / expressjs como respaldo, así que creé un servicio que devolvió una respuesta a la solicitud de OPCIONES que cubre todos los encabezados necesarios. -1 porque no podía usar esta respuesta para averiguar qué hacer, tuve que resolverlo yo mismo.
Ed Sykes
2
Sé que son más de 2 años después, pero ... El OP se refiere a las solicitudes GET varias veces (énfasis agregado por mí): «[...] la solicitud enviada al archivo de activos es una solicitud OPTIONS ( debería ser un GET solicitud ). » y «el navegador está tratando de averiguar si está" permitido "descargar el activo primero antes de realizar la solicitud GET ». ¿Cómo puede no ser un error de AngularJS entonces? ¿No deberían prohibirse las solicitudes de verificación previa para GET?
polettix
44
@polettix, incluso la solicitud GET puede activar la solicitud previa al vuelo en un navegador si se utilizan encabezados personalizados. Marque "solicitudes no tan simples" en el artículo que he vinculado: html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Una vez más, es el mecanismo del navegador , no AngularJS.
pkozlowski.opensource
70

Para Angular 1.2.0rc1 +, debe agregar una lista de recursos resourceUrlWhitelist.

1.2: versión de lanzamiento, agregaron una función escapeForRegexp para que ya no tenga que escapar de las cadenas. Puedes agregar la URL directamente

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

asegúrese de agregar ** para subcarpetas. Aquí hay un jsbin que funciona para 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: si todavía está en una versión rc, la solución Angular 1.2.0rc1 se ve así:

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

Aquí hay un ejemplo de jsbin donde funciona para 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pre 1.2: Para versiones anteriores (ref http://better-inter.net/enabling-cors-in-angular-js/ ) necesita agregar las siguientes 2 líneas a su configuración:

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

Aquí hay un ejemplo de jsbin donde funciona para versiones anteriores a 1.2: http://jsbin.com/olavok/11/edit

JStark
fuente
Esto funcionó para mí. Aquí está la magia apropiada para el lado del servidor con nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Esto solo funciona para la solicitud GET, aún no puede encontrar una solución para la solicitud POST en el dominio cruzado.
Pnct
2
La combinación de esta respuesta con la respuesta user2304582 anterior debería funcionar para las solicitudes POST. Tiene que decirle a su servidor que acepte solicitudes POST del servidor externo que lo envió
Charlie Martin
Esto no parece que funcione solo para mí ... así que un -1. Necesita algo en la misma URL que responda a una solicitud de OPCIONES como parte de una verificación previa al vuelo. ¿Puedes explicar por qué esto funcionaría?
Ed Sykes
1
@EdSykes, actualicé la respuesta para 1.2 y agregué un ejemplo de jsbin que funciona. Esperemos que eso lo resuelva por ti. Asegúrese de presionar el botón "Ejecutar con JS".
JStark
62

NOTA: No estoy seguro de que funcione con la última versión de Angular.

ORIGINAL:

También es posible anular la solicitud de OPCIONES (solo se probó en 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 = {};
}]);
usuario2899845
fuente
1
Funciona bien para mí con Chrome, FF e IE 10. IE 9 y versiones posteriores deben probarse de forma nativa en máquinas con Windows 7 o XP.
DOM
3
Alternativamente, uno puede configurar esto en el método de ese recurso $ solo y no globalmente:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r
3
¿Hay alguna trampa con esto? Parece tan simple y, sin embargo, la respuesta está muy en el fondo. editar: no funcionó en absoluto.
Sebastialonso
¿A dónde va este código? en app.js, controller.js o donde exactamente.
Murlidhar Fichadia
Este código no hace nada para obtener una solicitud. Agregué una regla separada para get también en mi configuración
kushalvm
34

Su servicio debe responder una OPTIONSsolicitud con encabezados como estos:

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]

Aquí hay un buen documento: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

usuario2304582
fuente
1
Para profundizar en la parte [la misma ACCESO-CONTROL-SOLICITUD-CABEZALES de la solicitud], como mencioné en otra respuesta, debe mirar la solicitud de OPCIONES que está agregando su navegador. Luego, agregue esos encabezados en el servicio que está creando (o servidor web). En expressjs que se parecía a: 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
El comentario de Ed Sykes es muy preciso, tenga en cuenta que los encabezados enviados en la respuesta OPTIONS y los enviados en la respuesta POST deben ser exactamente los mismos, por ejemplo: Access-Control-Allow-Origin: * no es lo mismo que Access- Control-Permitir-Origen: * debido a los espacios.
Lucia
20

El mismo documento dice

A diferencia de las solicitudes simples (discutidas anteriormente), las solicitudes "con verificación previa" primero envían un encabezado de solicitud de OPCIONES HTTP al recurso en el otro dominio, para determinar si la solicitud real es segura de enviar. Las solicitudes entre sitios se verifican previamente de esta manera, ya que pueden tener implicaciones para los datos del usuario. En particular, una solicitud se verifica previamente si:

Utiliza métodos distintos a GET o POST. Además, si POST se utiliza para enviar datos de solicitud con un tipo de contenido que no sea application / x-www-form-urlencoded, multipart / form-data o text / plain, por ejemplo, si la solicitud POST envía una carga XML al servidor usando application / xml o text / xml, la solicitud se verifica previamente.

Establece encabezados personalizados en la solicitud (por ejemplo, la solicitud utiliza un encabezado como X-PINGOTHER)

Cuando la solicitud original es Obtener sin encabezados personalizados, el navegador no debe realizar la solicitud de Opciones, como lo hace ahora. El problema es que genera un encabezado X-Requested-With que fuerza la solicitud de Opciones. Consulte https://github.com/angular/angular.js/pull/1454 sobre cómo eliminar este encabezado

Grum Ketema
fuente
10

Esto solucionó mi problema:

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

Si está utilizando un servidor nodeJS, puede usar esta biblioteca, funcionó bien para mí https://github.com/expressjs/cors

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

app.use(cors());

y después puedes hacer un npm update.

Zakaria.dem
fuente
4

Esta es la forma en que solucioné este problema en ASP.NET

  • Primero, debe agregar el paquete nuget Microsoft.AspNet.WebApi.Cors

  • Luego modifique el archivo App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Agregue este atributo en su clase de controlador

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Pude enviar a json a la acción de esta manera

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

Referencia: Habilitación de solicitudes de origen cruzado en ASP.NET Web API 2

asfez
fuente
2

De alguna manera lo arreglé cambiando

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

a

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

Perfectamente descrito en el comentario de pkozlowski. Tenía una solución de trabajo con AngularJS 1.2.6 y ASP.NET Web Api, pero cuando actualicé AngularJS a 1.3.3, las solicitudes fallaron.

  • La solución para el servidor Web Api fue agregar el manejo de las solicitudes de OPCIONES al comienzo del método de configuración (más información en esta publicación 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
fuente
Lo anterior no funciona para mí. Hay una solución mucho más simple para habilitar CORS para usar con AngularJS con ASP.NET WEB API 2.2 y posterior. Obtenga el paquete Microsoft WebAPI CORS de Nuget y luego en su archivo de configuración de WebAPI ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (cors); Los detalles están en el sitio de Microsoft aquí asp.net/web-api/overview/security/…
kmcnamee
0

Si está utilizando Jersey para REST API, puede hacer lo siguiente

No tiene que cambiar la implementación de sus servicios web.

Explicaré para Jersey 2.x

1) Primero agregue un ResponseFilter como se muestra a continuación

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) luego en el web.xml, en la declaración de servlet de jersey agregue lo siguiente

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

Dejé de intentar solucionar este problema.

Mi IIS web.config tenía el correspondiente "Access-Control-Allow-Methods " en él, experimenté agregando configuraciones de configuración a mi código Angular, pero después de quemar algunas horas tratando de hacer que Chrome llamara a un servicio web JSON entre dominios, me rendí miserablemente.

Al final, agregué una tonta página web de controlador ASP.Net, obtuve eso llamara a mi servicio web JSON y devolviera los resultados. Estuvo en funcionamiento en 2 minutos.

Aquí está el código que usé:

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

Y en mi controlador angular ...

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

Estoy seguro de que hay una forma más simple / genérica de hacerlo, pero la vida es demasiado corta ...

¡Esto funcionó para mí, y ahora puedo seguir haciendo el trabajo normal!

Mike Gledhill
fuente
0

Para un proyecto IIS MVC 5 / CLI angular (Sí, estoy consciente de que su problema es con Angular JS) con API, hice lo siguiente:

web.config debajo del <system.webServer>nodo

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

Eso debería solucionar sus problemas para MVC y WebAPI sin tener que hacer todo lo demás. Luego creé un HttpInterceptor en el proyecto Angular CLI que agregó automáticamente en la información de encabezado relevante. Espero que esto ayude a alguien en una situación similar.

Damon Drake
fuente
0

Poco tarde a la fiesta

Si está utilizando Angular 7 (o 5/6/7) y PHP como API y todavía recibe este error, intente agregar las siguientes opciones de encabezado al punto final (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");

Nota : Lo que solo requiere es Access-Control-Allow-Methods. Pero, estoy pegando aquí otros dos Access-Control-Allow-Originy Access-Control-Allow-Headers, simplemente porque necesitará que todos estos estén configurados correctamente para que Angular App se comunique correctamente con su API.

Espero que esto ayude a alguien.

Salud.

Anjana Silva
fuente