如何跳过OPTIONS预检请求?

108

我曾开发了一个 PhoneGap 应用程序,现在正在将其转换为移动网站。除了一个小故障外,一切都运行顺畅。我通过 POST 请求使用某个第三方 API,在应用程序中正常工作,但在移动网站版本中却失败了。

经过仔细查看,似乎 AngularJS(我猜测是浏览器)首先发送了一个 OPTIONS 请求。今天我对 CORS 有了很多了解,但似乎无法完全禁用它。我没有访问该 API 的权限(因此在该方面进行更改是不可能的),但他们已经将我正在使用的域添加到了他们的 Access-Control-Allow-Origin 标头。

这就是我所说的代码:

        var request = {
                language: 'fr',
                barcodes: [
                    {
                        barcode: 'somebarcode',
                        description: 'Description goes here'
                    }
                ]
            };
        }
        var config = {
            headers: { 
                'Cache-Control': 'no-cache',
                'Content-Type': 'application/json'
            }
        };
        $http.post('http://somedomain.be/trackinginfo', request, config).success(function(data, status) {
            callback(undefined, data);
        }).error(function(data, status) {
            var err = new Error('Error message');
            err.status = status;
            callback(err);
        });

我该如何防止浏览器(或AngularJS)发送那个OPTIONS请求,直接跳过实际的POST请求?我正在使用 AngularJS 1.2.0。

6个回答

118

预检请求是由于你的Content-Type为application/json而触发的。最简单的方法是将Content-Type设置为text/plain。在你的情况下,也可以使用application/x-www-form-urlencodedmultipart/form-data这两种内容类型,但需要适当格式化请求有效载荷。

如果您在进行此更改后仍然看到预检请求,则可能是Angular还添加了X头部到请求中。

或者您可能有一些标头(Authorization,Cache-Control等)也会触发它,请参见:


11
这是正确答案--您的Content-Type和Cache-Control头部信息触发了预检请求。仅使用Content-Type为text/plain等少数几种方式可以触发非预检请求的普通GET请求。参见:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#Preflighted_requests - Jeff Hubbard
啊,是的,我忘记了缓存控制。 - Ray Nicholus
23
自定义的头信息也会触发预检请求。 - Seb D.
8
更改内容类型以防止 OPTIONs 测试不是解决方法。内容类型应与内容类型匹配。 - ekerner
如果浏览器出现错误,并且在后端,HTTP方法OPTIONS被阻止,是否会产生任何影响,例如浏览器将不会调用相应的POST / PUT API,因为OPTIONS失败了? - Satish Patro

16

正如Ray所说,你可以通过修改content-header来停止它,例如 -

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

例如 -

angular.module('myApp').factory('User', ['$resource','$http',
    function($resource,$http){
        $http.defaults.headers.post["Content-Type"] = "text/plain";
        return $resource(API_ENGINE_URL+'user/:userId', {}, {
            query: {method:'GET', params:{userId:'users'}, isArray:true},
            getLoggedIn:{method:'GET'}
        });
    }]);

或者直接拨打电话 -

var req = {
 method: 'POST',
 url: 'http://example.com',
 headers: {
   'Content-Type': 'text/plain'
 },
 data: { test: 'test' }
}

$http(req).then(function(){...}, function(){...});

这不会发送任何预检选项请求。

注意:请求不应该有任何自定义的头部参数,如果请求头包含任何自定义头,则浏览器将发出预检请求,你无法避免它。


1
我应该如何仅使用$http来完成它? - learnercys
谢谢,这与我之前所做的相似。唯一的更改是使用 GET 方法和额外的头部 Authorization。但仍然发送预检请求。 - learnercys
1
你能把你的请求粘贴在这里吗?使用curl或其他工具。也许是因为Authorization头的原因,尝试将其删除然后再试一下。如果你正在发送自定义头,则Angular会发送预检请求。 - vivex
第一个是关于$http的请求,第二个是由Postman构建的有效请求 :) - learnercys

4

在执行某些类型的跨域AJAX请求时,支持CORS的现代浏览器会插入额外的“预检”请求来确定它们是否有权限执行该操作。 例如查询:

$http.get( ‘https://example.com/api/v1/users/’ +userId,
  {params:{
           apiKey:’34d1e55e4b02e56a67b0b66’
          }
  } 
);

由于这个片段,我们可以看到该地址发送了两个请求(OPTIONS和GET)。服务器的响应包括确认查询GET的可行性的标头。如果您的服务器未配置正确处理OPTIONS请求,则客户端请求将失败。例如:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, origin, x-requested-with, content-type
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Methods: OPTIONS
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Methods: GET
Access-Control-Allow-Methods: POST
Access-Control-Allow-Orgin: *
Access-Control-Max-Age: 172800
Allow: PUT
Allow: OPTIONS
Allow: POST
Allow: DELETE
Allow: GET

2

我认为最好的方法是检查请求是否为 "OPTIONS" 类型,并从中间件返回200。 这对我起了作用。

express.use('*',(req,res,next) =>{
      if (req.method == "OPTIONS") {
        res.status(200);
        res.send();
      }else{
        next();
      }
    });

它可以工作,但在OWASP中建议不要公开OPTIONS。您可以在后端/托管服务(如Nginx、Apache)中阻止它。 - Satish Patro

0

Preflight 是浏览器实现的一种网络安全功能。对于 Chrome,您可以通过添加 --disable-web-security 标志来禁用所有网络安全。

例如:"C:\Program Files\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C:\newChromeSettingsWithoutSecurity"。您可以先创建一个新的 Chrome 快捷方式,进入其属性并更改目标为上述内容。这应该会有所帮助!


5
你真的不能指望发帖者告诉他的客户关闭浏览器安全性以启用某项功能,对吧?! - svarog
2
@svarog 这主要是为了开发目的,大多数情况下在生产服务器上你不会遇到这个问题。 - Arijit Patra
如果浏览器报错并且在后端,HTTP OPTIONS方法被阻塞,那么会产生什么影响呢?例如OPTIONS失败后,浏览器是否无法调用相应的POST / PUT API? - Satish Patro

-3

将内容类型设置为未定义会使 JavaScript 传递头数据,覆盖默认的 Angular $httpProvider 头配置。Angular $http 文档

$http({url:url,method:"POST", headers:{'Content-Type':undefined}).then(success,failure);

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