AngularJS为一个跨源资源执行一个OPTIONS HTTP请求

我正在尝试设置AngularJS与交付我的模板文件的资产主机位于不同域的跨源资源进行通信,因此angular度执行的XHR请求必须跨域。 我已经添加了适当的CORS头到我的服务器的HTTP请求,使其工作,但它似乎并没有工作。 问题是,当我检查我的浏览器(铬)HTTP请求发送到资产文件的请求是一个OPTIONS请求(它应该是一个GET请求)。

我不确定这是否是AngularJS中的错误,或者是否需要configuration。 从我所了解的XHR包装器不能做一个OPTIONS HTTP请求,所以它看起来像浏览器试图找出是否“允许”下载资产之前执行GET请求。 如果是这种情况,那么是否需要使用资产主机设置CORS头(Access-Control-Allow-Origin: http : //asset.host .. )?

OPTIONS请求绝不是AngularJS错误,这就是跨源资源共享标准要求浏览器行为的原因。 请参阅此文档: https : //developer.mozilla.org/en-US/docs/HTTP_access_control ,在“概述”部分中的位置:

“跨源资源共享”标准的工作原理是添加新的HTTP标头,允许服务器使用Web浏览器描述允许读取该信息的原始集合。 此外,对于可能对用户数据造成副作用的HTTP请求方法(特别是对于GET以外的HTTP方法,或对于某些MIMEtypes的POST使用)。 该规范要求浏览器“预检”请求,使用HTTP OPTIONS请求头从服务器请求支持的方法,然后在从服务器“批准”时,用实际的HTTP请求方法发送实际的请求。 服务器还可以通知客户端“凭证”(包括Cookie和HTTPauthentication数据)是否应该与请求一起发送。

提供适用于所有WWW服务器的通用解决scheme是非常困难的,因为安装程序将根据您打算支持的服务器本身和HTTP动词而有所不同。 我会鼓励你find这篇精彩的文章( http://www.html5rocks.com/en/tutorials/cors/ ),其中有更多关于需要由服务器发送的确切标题的详细信息。

对于Angular 1.2.0rc1 +,你需要添加一个resourceUrlWhitelist。

1.2:发行版他们增加了一个escapeForRegexp函数,所以你不用再转义string了。 你可以直接添加url

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

确保为子文件夹添加**。 这是一个1.2的工作jsbin: http ://jsbin.com/olavok/145/edit


1.2.0rc:如果你仍然在一个RC版本,Angular 1.2.0rc1的解决scheme如下所示:

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

这里是一个jsbin的例子,它适用于1.2.0rc1: http ://jsbin.com/olavok/144/edit


Pre 1.2:对于旧版本(参考http://better-inter.net/enabling-cors-in-angular-js/ ),您需要将以下两行添加到您的configuration中:

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

这里有一个jsbin的例子,它适用于1.2以前的版本: http ://jsbin.com/olavok/11/edit

注意:不确定它适用于最新版本的Angular。

原版的:

也可以覆盖OPTIONS请求(仅在Chrome中进行testing):

 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 = {}; }]); 

您的服务必须使用以下标题回答OPTIONS请求:

 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] 

这是一个很好的文档: http : //www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

同一份文件说

与上面讨论的简单请求不同,“preflighted”请求首先向另一个域上的资源发送HTTP OPTIONS请求头,以确定实际的请求是否安全发送。 跨站点请求是这样预先考虑的,因为它们可能会影响用户数据。 特别是,如果出现以下情况,则会请求一个请求:

它使用GET或POST以外的方法。 另外,如果使用POST来发送具有除application / x-www-form-urlencoded,multipart / form-data或text / plain之外的Content-Type的请求数据,例如,如果POST请求将XML有效载荷发送到服务器使用application / xml或text / xml,那么请求是预冲的。

它在请求中设置自定义标题(例如,请求使用X-PINGOTHER等标题)

当原始请求是Get with without custom headers时,浏览器不应该进行Options请求。 问题是它生成一个头X-Requested-With强制Options请求。 有关如何删除此标题,请参阅https://github.com/angular/angular.js/pull/1454

这解决了我的问题:

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

如果你正在使用nodeJS服务器,你可以使用这个库,它工作正常我https://github.com/expressjs/cors

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

之后你可以做一个npm update

这是我在ASP.NET上解决这个问题的方式

  • 首先,你应该添加nuget包Microsoft.AspNet.WebApi.Cors

  • 然后修改文件App_Start \ WebApiConfig.cs

     public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.EnableCors(); ... } } 
  • 在你的控制器类中添加这个属性

     [EnableCors(origins: "*", headers: "*", methods: "*")] public class MyController : ApiController { [AcceptVerbs("POST")] public IHttpActionResult Post([FromBody]YourDataType data) { ... return Ok(result); } } 
  • 我能够通过这种方式发送json到行动

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

参考: 在ASP.NET Web API中启用跨域请求2

不知何故,我通过改变来修复它

Access-Control-Allow-Headers“Origin,X-Requested-With,Content-Type,Accept,Authorization”

Access-Control-Allow-Headers“Origin,Content-Type,Accept,Authorization”

在pkozlowski的评论完美描述。 我有AngularJS 1.2.6和ASP.NET Web Api的工作解决scheme,但是当我将AngularJS升级到1.3.3时,请求失败。

  • Web Api服务器的解决scheme是在configuration方法开始时添加对OPTIONS请求的处理( 本博文中有更多信息):

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

如果您正在使用Jersey for REST API,则可以执行以下操作

你不必改变你的web服务实现。

我将解释泽西2.x

1)首先添加一个ResponseFilter,如下所示

 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)然后在web.xml中,在运动衫的servlet声明中添加下面的内容

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

我放弃了试图解决这个问题。

我的IIS web.config中有相关的“ Access-Control-Allow-Methods ”,我尝试添加configuration设置到我的Angular的代码,但烧几个小时后,试图让Chrome调用跨域JSON Web服务,我悲惨地放弃了。

最后,我添加了一个愚蠢的ASP.Net处理程序网页,得到这个调用我的JSON Web服务,并返回结果。 它在2分钟内启动并运行。

这是我使用的代码:

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

而在我的angular控制器…

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

我确信有这样一个更简单/更通用的方法,但是人生太短暂了。

这对我有用,我现在可以继续做正常的工作!