AngularJS为跨域资源执行OPTIONS HTTP请求

268

我正在尝试设置AngularJS与跨域资源通信,其中提供模板文件的资产主机位于不同的域中,因此Angular执行的XHR请求必须是跨域的。 我已将适当的CORS标头添加到我的服务器上,以使HTTP请求正常工作,但似乎并没有起作用。 问题在于,当我检查浏览器(Chrome)中的HTTP请求时,发送到资产文件的请求是OPTIONS请求(应该是GET请求)。

我不确定这是否是AngularJS中的错误还是我需要配置某些内容。据我所了解,XHR包装器无法进行OPTIONS HTTP请求,因此看起来像是浏览器在执行GET请求之前先尝试确定是否“允许”下载该资产。如果是这种情况,则我是否还需要使用资产主机设置CORS标头(Access-Control-Allow-Origin:http://asset.host..)?

14个回答

228
OPTIONS请求绝不是AngularJS的错误,这是跨来源资源共享标准规定浏览器行为的方式。请参考此文档:https://developer.mozilla.org/en-US/docs/HTTP_access_control,其中在“概述”部分中提到:

  

通过添加新的HTTP头字段,跨源资源共享标准使服务器能够描述允许使用Web浏览器读取该信息的起源集合。此外,在可以对用户数据产生副作用的HTTP请求方法(特别是针对除GET之外的HTTP方法或带有某些MIME类型的POST使用)中。该规范要求浏览器使用HTTP OPTIONS请求头征询服务器支持的方法,然后,在服务器“批准”后,使用实际的HTTP请求方法发送实际请求。服务器还可以通知客户端是否应该发送“凭据”(包括Cookie和HTTP身份验证数据)以进行请求。

很难提供适用于所有WWW服务器的通用解决方案,因为设置将根据服务器本身和您打算支持的HTTP动词而有所不同。我建议您阅读这篇优秀文章(http://www.html5rocks.com/en/tutorials/cors/),其中包含有关服务器需要发送的确切标头的更多详细信息。


23
@matsko,您能详细说明一下您是如何解决这个问题的吗?我也遇到了同样的问题,即AngularJS的$resource _POST_请求会向我的后端ExpressJS服务器(在同一主机上但不同端口)生成一个_OPTIONS_请求。 - dbau
6
对于所有的反对者,几乎不可能为所有存在的Web服务器提供“精确”的配置设置——这种情况下答案需要写10页纸。相反,我已经链接到了一篇提供更多细节的文章。 - pkozlowski.opensource
5
你说得对,你不能指定一个答案——你需要给你的OPTIONS响应添加标题,覆盖浏览器请求的所有标题,在我的情况下,使用chrome时它是头文件'accept'和'x-requested-with'。在chrome中,我通过查看网络请求并查看chrome正在请求什么来找出我需要添加什么。我使用nodejs / expressjs作为后端,因此我创建了一个服务,返回一个涵盖所需所有标头的OPTIONS请求的响应。 -1因为我不能使用这个答案来弄清楚该怎么做,必须自己弄清楚。 - Ed Sykes
4
即使是 GET 请求,如果使用自定义标头,在浏览器中也可能触发预检请求。请查看我链接的文章中的“不那么简单的请求”部分:http://www.html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request。再次强调,这是浏览器的机制,而不是 AngularJS 的。 - pkozlowski.opensource
2
@pkozlowski.opensource 我一直以为GET请求总是“简单”的(假设),但事实并非如此:http://www.w3.org/TR/cors/ - 非常感谢让我再次确认,我学到了教训! - polettix
显示剩余5条评论

70

对于 Angular 1.2.0rc1+ 版本,您需要添加一个 resourceUrlWhitelist。

1.2: 发布版本中添加了一个 escapeForRegexp 函数,因此您不再需要转义字符串。 您可以直接添加 URL。

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

确保为子文件夹添加 **。这里有一个可用的1.2 jsbin: http://jsbin.com/olavok/145/edit


1.2.0rc:如果您仍在使用rc版本,则Angular 1.2.0rc1的解决方案如下:

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

以下是一个 jsbin 示例,可在 1.2.0rc1 版本中运行:http://jsbin.com/olavok/144/edit


1.2 之前的版本: 对于旧版本(参见 http://better-inter.net/enabling-cors-in-angular-js/),您需要将以下两行添加到您的配置文件中:

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

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


14
仅适用于GET请求,仍无法解决跨域POST请求的问题。 - Pnctovski
2
将此答案与上面用户2304582的答案相结合,应该适用于POST请求。您必须告诉您的服务器接受来自发送它的外部服务器的POST请求。 - Charlie Martin
这对我来说看起来似乎无法单独运行...所以是-1。你需要在相同的URL上添加一个能够响应OPTIONS请求的内容,作为预检查的一部分。你能解释一下为什么这样会起作用吗? - Ed Sykes
1
@EdSykes,我已经更新了1.2的答案,并添加了一个可工作的jsbin示例。希望这能解决你的问题。请确保按下“Run with JS”按钮。 - JStark
对于1.2之前的版本,删除"X-Requested-With"会防止服务器检测到ajax请求。这不是一个很好的解决方案。 - Daniel Schaffer
显示剩余4条评论

63

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

也可以覆盖OPTIONS请求(仅在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 = {};
}]);

1
对我来说,在Chrome、FF和IE 10中都可以正常工作。IE 9及以下版本应该在Windows 7或XP计算机上进行本地测试。 - DOM
3
或者,您可以单独在该$resource的方法上设置它,而不是全局设置:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'}); - h7r
3
这个有什么陷阱吗?看起来很简单,但答案深藏在帖子里?编辑:完全没用。 - Sebastialonso
这段代码应该放在哪里?是在 app.js、controller.js 还是其他具体的位置。 - Murlidhar Fichadia
这段代码对于GET请求没有任何作用。我在我的配置中添加了一个单独的规则来处理GET请求。 - HalfWebDev
显示剩余2条评论

34

1
为了详细说明“从请求中获取相同的ACCESS-CONTROL-REQUEST-HEADERS”这一部分,正如我在另一个答案中提到的那样,您需要查看浏览器添加的OPTIONS请求。然后,在您正在构建的服务(或Web服务器)上添加这些标头。在expressjs中,它看起来像这样: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-requested-with'); response.send(); }); - Ed Sykes
1
Ed Sykes的评论非常准确,请注意OPTIONS响应中发送的头部和POST响应中发送的头部应该完全相同,例如:Access-Control-Allow-Origin:* 和 Access-Control-Allow-Origin : * 是不同的,因为存在空格的原因。 - Lucia

20

同一份文件提到:

与简单请求不同(如上所述),“预检”请求首先向在另一个域上的资源发送HTTP OPTIONS请求标头,以确定实际请求是否安全。这样做的原因是跨站点请求可能会对用户数据产生影响。特别地,如果请求:

使用除GET或POST之外的方法。此外,如果使用POST发送请求数据的Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain,例如,如果POST请求使用application/xml或text/xml向服务器发送XML负载,则该请求将被预检。

设置了自定义标头(例如,请求使用诸如X-PINGOTHER之类的标头)

当原始请求是没有自定义标头的Get请求时,浏览器不应该发出Options请求,但现在它确实会生成一个强制进行Options请求的X-Requested-With标头。请参见https://github.com/angular/angular.js/pull/1454,了解如何删除此标头。


10

这解决了我的问题:

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

10
如果您正在使用nodeJS服务器,您可以使用此库,对我来说效果很好 https://github.com/expressjs/cors
var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

然后您可以执行 npm update


5

以下是我在ASP.NET上解决此问题的方法:

  • First, you should add the nuget package Microsoft.AspNet.WebApi.Cors

  • Then modify the file App_Start\WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
    
  • Add this attribute on your controller class

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
    
  • I was able to send json to the action by this way

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

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


2
不知怎么的,我通过更改

进行了修复。

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

to

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

0

我放弃尝试修复这个问题。

我的 IIS web.config 中已经包含了相关的"Access-Control-Allow-Methods",我试图在 Angular 代码中添加配置设置,但是在花费了几个小时试图让 Chrome 调用一个跨域的 JSON web service 后,我非常痛苦地放弃了。

最终,我添加了一个愚蠢的 ASP.Net 处理程序网页,让它调用我的 JSON web service 并返回结果。两分钟后它就可以运行了。

以下是我使用的代码:

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

我相信有一种更简单/通用的方法来做这件事,但是人生太短暂了...

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接