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

Posted

技术标签:

【中文标题】AngularJS 对跨域资源执行 OPTIONS HTTP 请求【英文标题】:AngularJS performs an OPTIONS HTTP request for a cross-origin resource 【发布时间】:2012-08-20 04:01:21 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

跨域资源共享标准通过添加新的 HTTP 来工作 允许服务器描述源集的标头 允许使用网络浏览器阅读该信息。此外, 对于可能对用户数据造成副作用的 HTTP 请求方法(在 特定;对于 GET 以外的 HTTP 方法,或对于 POST 使用 某些 MIME 类型)。该规范要求浏览器 “预检”请求,从服务器请求支持的方法 带有 HTTP OPTIONS 请求标头,然后在“批准”时 服务器,使用实际的 HTTP 请求发送实际的请求 方法。服务器还可以通知客户端是否“凭据” (包括 Cookie 和 HTTP 身份验证数据)应与 请求。

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

【讨论】:

@matsko 你能详细说明你做了什么来解决这个问题吗?我面临同样的问题,AngularJS $resource POST 请求正在向我的后端 ExpressJS 服务器生成一个 OPTIONS 请求(在同一主机上;但端口不同) . 对于所有反对者——几乎不可能为所有网络服务器提供准确配置设置——在这种情况下,答案需要 10 页。相反,我链接到提供更多详细信息的文章。 您是对的,您无法给出答案 - 您需要在 OPTIONS 响应中添加标题,该响应涵盖浏览器请求的所有标题,在我的情况下,使用 chrome标题“接受”和“x-requested-with”。在 chrome 中,我通过查看网络请求并查看 chrome 要求的内容来确定需要添加的内容。我使用 nodejs/expressjs 作为支持,因此我创建了一个服务,该服务返回对 OPTIONS 请求的响应,该请求涵盖了所需的所有标头。 -1 因为我无法使用这个答案来弄清楚该怎么做,必须自己弄清楚。 我知道它是 2 年多以后,但是...... OP 多次引用 GET 请求(强调由我添加):«[...] 发送到资产文件的请求是OPTIONS 请求(应该是 GET 请求)。»并且«浏览器试图确定是否“允许”首先下载资产在它执行 GET 请求之前»。那么它怎么可能不是 AngularJS 的错误呢? GET不应该禁止预检请求吗? @polettix 如果使用自定义标头,即使 GET 请求在浏览器中也可以触发飞行前请求。检查我链接的文章中的“不那么简单的请求”:html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request。再一次,它是浏览器的机制,而不是AngularJS。【参考方案2】:

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

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

'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/]);
 ])

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


Pre 1.2:对于旧版本(参考 http://better-inter.net/enabling-cors-in-angular-js/),您需要在配置中添加以下 2 行:

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

这是一个适用于 1.2 之前版本的 jsbin 示例: http://jsbin.com/olavok/11/edit

【讨论】:

这对我有用。这是使用 nodejs/express 的服务器端的适当魔法:gist.github.com/dirkk0/5967221 这仅适用于GET请求,仍然无法找到跨域POST请求的解决方案。 将此答案与上面的 user2304582 答案结合起来应该适用于 POST 请求。您必须告诉您的服务器接受来自发送它的外部服务器的 POST 请求 这对我来说似乎不会自行工作......所以是-1。作为飞行前检查的一部分,您需要在同一 URL 上响应 OPTIONS 请求的内容。你能解释一下为什么会这样吗? @EdSykes ,我已经更新了 1.2 的答案并添加了一个有效的 jsbin 示例。希望这应该为您解决。确保按下“使用 JS 运行”按钮。【参考方案3】:

注意:不确定它是否适用于最新版本的 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 = ;
]);

【讨论】:

在 Chrome、FF 和 IE 10 上运行良好。IE 9 及更低版本应在 Windows 7 或 XP 机器上进行本机测试。 或者,可以单独在 $resource 的方法上而不是全局上设置它:$resource('http://yourserver/yourentity/:id', , query: method: 'GET'); 这有什么问题吗?看起来很简单,但答案却如此深入人心?编辑:根本没用。 这段代码去哪儿了?在 app.js、controller.js 或确切的位置。 此代码对获取请求没有任何作用。我在我的配置中添加了单独的 get 规则【参考方案4】:

您的服务必须回答带有以下标头的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

【讨论】:

要详细说明 [the same ACCESS-CONTROL-REQUEST-HEADERS from request] 部分,正如我在另一个答案中提到的,您需要查看浏览器正在添加的 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 的评论非常准确,注意 OPTIONS 响应中发送的 headers 和 POST 响应中发送的 headers 应该完全相同,例如:Access-Control-Allow-Origin: * 与 Access-Control-Allow-Origin 不同:* 因为有空格。【参考方案5】:

同一份文件说

与简单请求(上面讨论过)不同,“预检”请求首先向另一个域上的资源发送一个 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 请求。问题是它会生成一个标题 X-Requested-With 来强制选项请求。请参阅https://github.com/angular/angular.js/pull/1454 了解如何删除此标头

【讨论】:

【参考方案6】:

这解决了我的问题:

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

【讨论】:

【参考方案7】:

如果你使用的是 nodeJS 服务器,你可以使用这个库,它对我来说很好https://github.com/expressjs/cors

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

app.use(cors());

然后你可以做一个npm update

【讨论】:

【参考方案8】:

这是我在 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(...)

参考: Enabling Cross-Origin Requests in ASP.NET Web API 2

【讨论】:

【参考方案9】:

不知何故,我通过更改修复了它

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

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

【讨论】:

【参考方案10】:

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

Web Api 服务器的解决方案是在配置方法的开头添加对 OPTIONS 请求的处理(更多信息in this blog post):

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

【讨论】:

以上对我不起作用。有一个更简单的解决方案可以让 CORS 与 AngularJS 和 ASP.NET WEB API 2.2 及更高版本一起使用。从 Nuget 获取 Microsoft WebAPI CORS 包,然后在您的 WebAPI 配置文件中... var cors = new EnableCorsAttribute("www.example.com", "", ""); config.EnableCors(cors);详细信息在微软网站上asp.net/web-api/overview/security/…【参考方案11】:

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

您不必更改您的网络服务实现。

我会解释 Jersey 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 中,在 jersey servlet 声明中添加以下内容

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

【讨论】:

【参考方案12】:

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

我的 IIS web.config 中有相关的“Access-Control-Allow-Methods”,我尝试将配置设置添加到我的 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) 
      ....
   );

我确信有一种更简单/更通用的方法可以做到这一点,但是生命太短了......

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

【讨论】:

【参考方案13】:

对于带有 API 的 IIS MVC 5 / Angular CLI(是的,我很清楚您的问题出在 Angular JS)项目,我做了以下操作:

web.config&lt;system.webServer&gt; 节点下

    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, atv2" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
    </httpProtocol>

global.asax.cs

protected void Application_BeginRequest() 
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") 
    Response.Flush();
    Response.End();
  

这应该可以解决 MVC 和 WebAPI 的问题,而无需执行所有其他操作。然后我在 Angular CLI 项目中创建了一个 HttpInterceptor,它自动添加了相关的标头信息。希望这可以帮助遇到类似情况的人。

【讨论】:

【参考方案14】:

晚会有点晚了,

如果您使用 Angular 7(或 5/6/7)和 php 作为 API 并且仍然收到此错误,请尝试将以下标头选项添加到端点 (PHP API)。

 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 App 正确地与您的 API 对话。

希望这对某人有所帮助。

干杯。

【讨论】:

以上是关于AngularJS 对跨域资源执行 OPTIONS HTTP 请求的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS 对跨域资源执行 OPTIONS HTTP 请求 => 错误 401

跨域请求触发浏览器复杂请求

检测浏览器对跨域 XMLHttpRequests 的支持?

怎样避免跨域发出OPTIONS请求?

TryGetFormCredentials 对跨域请求返回 null

如何使用 Joomla 作为服务器配置跨域资源共享 (CORS)?