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

Intento configurar AngularJS para que se comunique 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 de XHR que realiza un angular debe ser de dominio cruzado. He agregado el encabezado CORS adecuado a mi servidor para que la solicitud HTTP lo haga funcionar, 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 OPTIONS (debe 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 hacer una solicitud HTTP de OPCIONES para que parezca que el navegador está tratando de averiguar si se “permite” descargar el activo primero antes de realizar la solicitud GET. Si este es el caso, entonces ¿necesito configurar el encabezado CORS (Access-Control-Allow-Origin: http://asset.host … ) con el host de activos también?

La solicitud de OPTIONS no es de ninguna manera un error de AngularJS, así es como el estándar de Intercambio de recursos de origen cruzado obliga a los navegadores a comportarse. 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 de Intercambio de recursos de origen cruzado funciona al agregar nuevos encabezados HTTP que permiten a los servidores describir el conjunto de orígenes que tienen permiso para leer esa información mediante 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 los métodos HTTP distintos de GET, o para el uso POST con ciertos tipos MIME). La especificación exige que los navegadores “realicen la verificación previa” de la solicitud, solicitando los métodos compatibles del servidor con un encabezado de solicitud HTTP OPTIONS 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 las solicitudes.

Es muy difícil proporcionar una solución genérica que funcione para todos los servidores WWW, ya que la configuración variará dependiendo del servidor en sí y de los verbos HTTP que pretenda admitir. Le recomiendo que supere este excelente artículo ( http://www.html5rocks.com/en/tutorials/cors/ ) que contiene muchos más detalles sobre los encabezados exactos que debe enviar un servidor.

Para Angular 1.2.0rc1 +, necesita agregar un resourceUrlWhitelist.

1.2: versión de lanzamiento que agregaron una función escapeForRegexp por lo que ya no tiene que escapar de las cadenas. Puedes agregar la url directamente

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

asegúrese de agregar ** para las subcarpetas. Aquí hay un jsbin 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 es la siguiente:

 .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

NOTA: No estoy seguro si funciona 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 = {}; }]); 

Su servicio debe responder una solicitud de OPTIONS 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

El mismo documento dice

A diferencia de las solicitudes simples (discutidas anteriormente), las solicitudes “pre-iluminadas” primero envían un encabezado de solicitud HTTP OPTIONS al recurso en el otro dominio, para determinar si la solicitud real es segura de enviar. Las solicitudes entre sitios se preludian de esta manera, ya que pueden tener implicaciones para los datos del usuario. En particular, una solicitud se realiza de antemano si:

Utiliza métodos distintos de GET o POST. Además, si se utiliza POST para enviar datos de solicitud con un tipo de contenido distinto de 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, entonces la solicitud se realiza de antemano.

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 hacer que la solicitud de Opciones lo haga ahora. El problema es que genera un encabezado X-Requested-With que fuerza la solicitud de Opciones. Ver https://github.com/angular/angular.js/pull/1454 sobre cómo eliminar este encabezado

Esto solucionó mi problema:

 $http.defaults.headers.post["Content-Type"] = "text/plain"; 

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 de que puedas hacer una npm update .

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

  • En primer lugar, debe agregar el paquete nuget Microsoft.AspNet.WebApi.Cors

  • A continuación, modifique el archivo App_Start \ WebApiConfig.cs

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

     [EnableCors(origins: "*", headers: "*", methods: "*")] public class MyController : ApiController { [AcceptVerbs("POST")] public IHttpActionResult Post([FromBody]YourDataType data) { ... return Ok(result); } } 
  • Pude enviar 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

De alguna manera lo arreglé cambiando

Access-Control-Allow-Headers “Origen, X-Solicitado-Con, Tipo de contenido, Aceptar, Autorización”

a

Access-Control-Allow-Headers “Origen, tipo de contenido, aceptar, autorización”

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 había actualizado AngularJS a 1.3.3, las solicitudes fallaban.

  • La solución para el servidor Web Api fue agregar el manejo de las solicitudes OPTIONS 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(); }); 

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

No tiene que cambiar la implementación de su servicio web.

Voy a explicar por Jersey 2.x

1) Primero agregue un filtro de respuesta 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 web.xml, en la statement del servlet de jersey agrega el siguiente

   jersey.config.server.provider.classnames YOUR PACKAGE.CorsResponseFilter  

Dejé de intentar solucionar este problema.

Mi IIS web.config tenía el ” Access-Control-Allow-Methods ” relevante en él, experimenté añadiendo configuraciones a mi código Angular, pero después de grabar unas horas tratando de que Chrome llamara a un servicio web JSON multidominio, Me rendí miserablemente.

Al final, agregué una estúpida página web de manejador de ASP.Net, conseguí que llamara a mi servicio web JSON y devolviera los resultados. Estuvo en funcionamiento en 2 minutos.

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

 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 / más genérica de hacerlo, pero la vida es demasiado corta …

¡Esto funcionó para mí, y puedo continuar haciendo un trabajo normal ahora!