关于cors:AngularJS为跨源资源执行OPTIONS HTTP请求

AngularJS performs an OPTIONS HTTP request for a cross-origin resource

我正在尝试设置AngularJS与跨源资源通信,在该资源中,交付我的模板文件的资产主机位于不同的域上,因此Angular执行的XHR请求必须是跨域的。我已经为HTTP请求向我的服务器添加了适当的CORS头,以使其正常工作,但它似乎不工作。问题是,当我检查浏览器(chrome)中的HTTP请求时,发送到资产文件的请求是一个选项请求(它应该是一个get请求)。

我不确定这是AngularJS中的一个bug还是需要配置一些东西。据我所知,XHR包装器不能发出选项HTTP请求,因此看起来浏览器在执行GET请求之前,正在尝试确定是否允许先下载资产。如果是这种情况,那么是否还需要将CORS头文件(访问控制允许来源:http://as set.host…)与资产主机一起设置?


选项请求决不是AngularJS的bug,这是跨源代码资源共享标准授权浏览器行为的方式。请参考本文档:https://developer.mozilla.org/en-us/docs/http_access_control,在"概述"部分中,它指出:

The Cross-Origin Resource Sharing standard works by adding new HTTP
headers that allow servers to describe the set of origins that are
permitted to read that information using a web browser. Additionally,
for HTTP request methods that can cause side-effects on user data (in
particular; for HTTP methods other than GET, or for POST usage with
certain MIME types). The specification mandates that browsers
"preflight" the request, soliciting supported methods from the server
with an HTTP OPTIONS request header, and then, upon"approval" from
the server, sending the actual request with the actual HTTP request
method. Servers can also notify clients whether"credentials"
(including Cookies and HTTP Authentication data) should be sent with
requests.

很难提供适用于所有WWW服务器的通用解决方案,因为安装程序将根据服务器本身和您打算支持的HTTP谓词而有所不同。我鼓励您阅读这篇优秀的文章(http://www.html5rocks.com/en/tutorials/cors/),其中详细介绍了服务器需要发送的确切头文件。


对于Angular 1.2.0RC1+,需要添加ResourceUrlWhiteList。

1.2:发布版本,他们添加了一个escapeforregexp函数,这样您就不再需要转义字符串了。您可以直接添加URL

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

确保为子文件夹添加**。下面是1.2的工作JSbin:http://jsbin.com/olavok/145/edit/编辑

1.2.0RC:如果您仍然使用RC版本,则角度为1.2.0RC1,解决方案如下:

1
2
3
.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/),您需要在配置中添加以下两行:

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

下面是一个jsbin示例,它适用于1.2之前的版本:http://jsbin.com/olavok/11/edit/编辑


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

原件:

也可以覆盖选项请求(仅在chrome中测试):

1
2
3
4
5
6
7
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请求:

1
2
3
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向服务器添加cors支持


同一份文件上写着

与上面讨论的简单请求不同,"预照亮"请求首先将HTTP选项请求头发送到另一个域上的资源,以确定实际请求是否安全发送。跨站点请求是这样预先设置的,因为它们可能会影响用户数据。特别是,如果出现以下情况,请求将被预先点亮:

It uses methods other than GET or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.

It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)

如果原始请求没有自定义头,则浏览器不应发出选项请求,而应立即发出请求。问题是它生成了一个头x-requested-with,用于强制选项请求。有关如何删除此头的信息,请参阅https://github.com/angular/angular.js/pull/1454。


如果您使用的是nodejs服务器,那么您可以使用这个库,它对我来说工作得很好https://github.com/expressjs/cors

1
2
3
4
5
var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

然后你可以做一个npm update


这解决了我的问题:

10


以下是我在ASP.NET上修复此问题的方法

  • 首先,您应该添加nuget包microsoft.aspnet.webapi.cors

  • 然后修改文件app_startwebapiconfig.cs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();

          ...
       }    
    }
  • 在控制器类上添加此属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [EnableCors(origins:"*", headers:"*", methods:"*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • 我可以用这种方式把JSON发送到行动中

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

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


不知怎么地,我通过改变

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

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

派对有点晚了,

如果您使用angular 7(或5/6/7)和php作为API,但仍然出现此错误,请尝试向端点(phapi)添加以下头选项。

1
2
3
 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");

注:只需要Access-Control-Allow-Methods。但是,我在这里粘贴另外两个Access-Control-Allow-OriginAccess-Control-Allow-Headers,仅仅是因为您需要正确设置所有这些,以便Angular应用程序能够正确地与您的API对话。

希望这能帮助别人。

干杯。


对于IIS MVC 5/Angular CLI(是的,我很清楚您的问题在于Angular JS)项目,我使用API执行了以下操作:

节点下的web.config

1
2
3
4
5
6
7
8
9
10
11
12
    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
       
       
       
      </customHeaders>
    </httpProtocol>

全球ASC.CS

1
2
3
4
5
6
protected void Application_BeginRequest() {
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod =="OPTIONS") {
    Response.Flush();
    Response.End();
  }
}

这应该可以解决MVC和WebAPI的问题,而不必执行所有其他的运行。然后,我在angular cli项目中创建了一个httpinterceptor,该项目自动添加到相关的头信息中。希望这能在类似的情况下帮助别人。


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

我的iis web.config中有相关的"Access-Control-Allow-Methods",我尝试在角度代码中添加配置设置,但是在花了几个小时试图让chrome调用跨域JSON Web服务后,我不幸地放弃了。

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

这是我使用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
        }
    }
}

在我的角度控制器里…

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

我相信有一种更简单/更一般的方法可以做到这一点,但生活太短了…

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


如果您将Jersey用于REST API,可以执行以下操作

您不必更改WebServices实现。

我来解释一下泽西2.x

1)首先添加一个响应过滤器,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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中,在jersey servlet声明中添加以下内容

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

在普科兹洛夫斯基的评论中完美地描述了这一点。我使用了AngularJS 1.2.6和ASP.NET Web API的工作解决方案,但当我将AngularJS升级到1.3.3时,请求失败。

  • Web API服务器的解决方案是在配置方法的开头添加对选项请求的处理(此日志中的详细信息):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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();
    });