AngularJS executa uma solicitação HTTP OPTIONS para um recurso de origem cruzada

Estou tentando configurar o AngularJS para se comunicar com um recurso de origem cruzada, em que o host de recurso que entrega meus arquivos de modelo está em um domínio diferente e, portanto, a solicitação XHR que o desempenho angular deve ter entre domínios. Eu adicionei o header CORS apropriado ao meu servidor para a solicitação HTTP para que isso funcione, mas parece que não funciona. O problema é que, quando inspeciono as solicitações HTTP no meu navegador (chrome), a solicitação enviada ao arquivo de ativos é uma solicitação OPTIONS (deve ser uma solicitação GET).

Não tenho certeza se isso é um bug no AngularJS ou se preciso configurar algo. Pelo que entendi, o wrapper XHR não pode fazer uma solicitação HTTP OPTIONS, então parece que o navegador está tentando descobrir se é “permitido” fazer o download do ativo antes de executar a solicitação GET. Se este for o caso, então eu preciso definir o header CORS (Access-Control-Allow-Origin: http://asset.host … ) Com o host do recurso também?

A requisição OPTIONS não é de forma alguma um bug do AngularJS, é como o padrão de compartilhamento de resources de origem cruzada exige que os navegadores se comportem. Por favor, consulte este documento: https://developer.mozilla.org/pt-BR/docs/HTTP_access_control , onde na seção “Visão geral” diz:

O padrão Cross-Origin Resource Sharing funciona adicionando novos headers HTTP que permitem que os servidores descrevam o conjunto de origens com permissão para ler essas informações usando um navegador da web. Além disso, para methods de solicitação HTTP que podem causar efeitos colaterais nos dados do usuário (em particular; para methods HTTP diferentes de GET ou para uso POST com determinados tipos MIME). A especificação exige que os navegadores “preflight” a solicitação, solicitando methods suportados do servidor com um header de solicitação HTTP OPTIONS e, após a “aprovação” do servidor, enviem a solicitação real com o método de solicitação HTTP real. Os servidores também podem notificar os clientes se “credenciais” (incluindo cookies e dados de autenticação HTTP) devem ser enviadas com solicitações.

É muito difícil fornecer uma solução genérica que funcione para todos os servidores WWW, pois a configuração irá variar dependendo do próprio servidor e dos verbos HTTP que você pretende suportar. Gostaria de incentivá-lo a superar este excelente artigo ( http://www.html5rocks.com/en/tutorials/cors/ ) que tem muito mais detalhes sobre os headers exatos que precisam ser enviados por um servidor.

Para o Angular 1.2.0rc1 + você precisa adicionar um resourceUrlWhitelist.

1.2: versão de lançamento eles adicionaram uma function escapeForRegexp para que você não precise mais escaping das strings. Você pode simplesmente adicionar o URL diretamente

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

Certifique-se de adicionar ** para subpastas. Aqui está um jsbin funcional para 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Se você ainda está em uma versão rc, o Angular 1.2.0rc1 se parece com a solução:

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

Aqui está um exemplo de jsbin onde funciona para 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pré 1.2: Para versões mais antigas (ref http://better-inter.net/enabling-cors-in-angular-js/ ) você precisa adicionar as seguintes 2 linhas à sua configuração:

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

Aqui está um exemplo jsbin onde funciona para as versões anteriores à 1.2: http://jsbin.com/olavok/11/edit

NOTA: Não tenho certeza se funciona com a versão mais recente do Angular.

ORIGINAL:

Também é possível replace a solicitação OPTIONS (só foi testada no 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 = {}; }]); 

Seu serviço deve responder a uma solicitação OPTIONS com headers como estes:

 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] 

Aqui está um bom documento: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

O mesmo documento diz

Ao contrário das solicitações simples (discutidas acima), as solicitações “preflighted” primeiro enviam um header de solicitação HTTP OPTIONS para o recurso no outro domínio, para determinar se a solicitação real é segura para enviar. As solicitações entre sites são pré-formatadas assim, pois podem ter implicações nos dados do usuário. Em particular, uma solicitação é pré-determinada se:

Ele usa methods diferentes de GET ou POST. Além disso, se o POST for usado para enviar dados de solicitação com um tipo de conteúdo diferente de application / x-www-form-urlencoded, multipart / form-data ou text / plain, por exemplo, se a solicitação POST enviar uma carga XML ao servidor usando application / xml ou text / xml, a solicitação é preflighted.

Ele define headers personalizados na solicitação (por exemplo, a solicitação usa um header como X-PINGOTHER)

Quando a solicitação original é Get sem headers personalizados, o navegador não deve solicitar Opções, o que faz agora. O problema é que ele gera um header X-Requested-With que força a solicitação Options. Veja https://github.com/angular/angular.js/pull/1454 sobre como remover este header

Isso resolveu meu problema:

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

Se você estiver usando um servidor nodeJS, você pode usar esta biblioteca, funcionou bem para mim https://github.com/expressjs/cors

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

e depois você pode fazer uma npm update .

Aqui está a maneira que eu consertei esse problema no ASP.NET

  • Primeiro, você deve adicionar o pacote nuget Microsoft.AspNet.WebApi.Cors

  • Em seguida, modifique o arquivo App_Start \ WebApiConfig.cs

     public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.EnableCors(); ... } } 
  • Adicione este atributo na sua class de controlador

     [EnableCors(origins: "*", headers: "*", methods: "*")] public class MyController : ApiController { [AcceptVerbs("POST")] public IHttpActionResult Post([FromBody]YourDataType data) { ... return Ok(result); } } 
  • Consegui enviar json para a ação dessa forma

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

Referência: habilitando solicitações de origem cruzada na API da Web do ASP.NET 2

De alguma forma eu consertei mudando

Access-Control-Allow-Headers “Origem, Solicitado-Com-X, Tipo-de-Conteúdo, Aceitar, Autorização”

para

Access-Control-Allow-Headers “Origem, Tipo de Conteúdo, Aceitar, Autorização”

Perfeitamente descrito no comentário do pkozlowski. Eu tinha solução de trabalho com AngularJS 1.2.6 e ASP.NET Web Api, mas quando eu tinha atualizado AngularJS para 1.3.3, em seguida, os pedidos falharam.

  • A solução para o servidor Web Api foi adicionar a manipulação das solicitações OPTIONS no início do método de configuração (mais informações nesta postagem do 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(); }); 

Se você estiver usando Jersey para API REST, você pode fazer o seguinte

Você não precisa alterar a implementação de seus serviços da web.

Eu vou explicar para Jersey 2.x

1) Primeiro adicione um ResponseFilter como mostrado abaixo

 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) depois no web.xml, na declaração do servlet jersey adicione o seguinte

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

Eu desisti de tentar corrigir esse problema.

Meu web.config do IIS tinha o ” Access-Control-Allow-Methods ” relevante nele, experimentei adicionar configurações ao meu código Angular, mas depois de algumas horas tentando fazer o Chrome chamar um serviço da Web JSON de vários domínios, Eu desisti miseravelmente.

No final, adicionei uma página da Web manipuladora ASP.Net idiota, consegui chamar meu serviço da Web JSON e retornar os resultados. Foi instalado e funcionando em 2 minutos.

Aqui está o código que usei:

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

E no meu controlador angular …

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

Tenho certeza que há uma maneira mais simples / genérica de fazer isso, mas a vida é muito curta …

Isso funcionou para mim, e eu posso continuar fazendo o trabalho normal agora !!