尝试使用 fetch 和 pass in 模式:no-cors

IT技术 javascript reactjs cors create-react-app fetch-api
2021-01-20 12:50:58

我可以http://catfacts-api.appspot.com/api/facts?number=99通过 Postman 到达这个端点,然后它返回JSON

此外,我正在使用 create-react-app 并希望避免设置任何服务器配置。

在我的客户端代码中,我试图用来fetch做同样的事情,但我收到错误:

请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,不允许访问Origin ' http://localhost:3000 '。如果不透明响应满足您的需求,请将请求的模式设置为“no-cors”以在禁用 CORS 的情况下获取资源。

所以我试图将一个对象传递给我的 Fetch,这将禁用 CORS,如下所示:

fetch('http://catfacts-api.appspot.com/api/facts?number=99', { mode: 'no-cors'})
  .then(blob => blob.json())
  .then(data => {
    console.table(data);
    return data;
  })
  .catch(e => {
    console.log(e);
    return e;
  });

有趣的是,我得到的错误实际上是这个函数的语法错误。我不确定我的实际fetch是否已损坏,因为当我删除 { mode: 'no-cors' } 对象并为其提供不同的 URL 时,它工作得很好。

我也尝试传入 object { mode: 'opaque'},但这会从上面返回原始错误。

我相信我需要做的就是禁用 CORS .. 我错过了什么?

6个回答

mode: 'no-cors'不会神奇地使事情起作用。事实上,这让事情变得更糟,因为它的一种效果是告诉浏览器,“在任何情况下都阻止我的前端 JavaScript 代码查看响应正文和标头的内容。” 当然,你永远不想那样。

来自前端 JavaScript 的跨域请求发生的情况是浏览器默认阻止前端代码访问跨域资源。如果Access-Control-Allow-Origin在响应中,则浏览器会放宽阻止并允许您的代码访问响应。

但是,如果站点Access-Control-Allow-Origin在其响应中发送 no ,则您的前端代码无法直接访问来自该站点的响应。特别是,您无法通过指定来修复它mode: 'no-cors'(实际上这将确保您的前端代码无法访问响应内容)。

但是,有一件事奏效:如果您通过CORS 代理发送请求

您还可以在短短 2-3 分钟内轻松将自己的代理部署到 Heroku,使用 5 个命令:

git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
heroku create
git push heroku master

运行这些命令后,您最终将拥有自己的 CORS Anywhere 服务器,例如,运行在https://cryptic-headland-94862.herokuapp.com/.

使用您的代理 URL 作为您的请求 URL 的前缀;例如:

https://cryptic-headland-94862.herokuapp.com/https://example.com

添加代理 URL 作为前缀会导致通过您的代理发出请求,其中:

  1. 将请求转发到https://example.com.
  2. 接收来自 的响应https://example.com
  3. Access-Control-Allow-Origin标头添加到响应中。
  4. 将带有添加的标头的响应传递回请求前端代码。

然后浏览器允许前端代码访问响应,因为带有Access-Control-Allow-Origin响应头响应是浏览器看到的。

即使请求是触发浏览器执行 CORS 预检OPTIONS请求的请求,这也有效,因为在这种情况下,代理还会发回使预检成功所需Access-Control-Allow-HeadersAccess-Control-Allow-Methods标头。


我可以http://catfacts-api.appspot.com/api/facts?number=99通过邮递员到达这个端点

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS解释了为什么即使您可以使用 Postman 访问响应,浏览器也不会让您从前端访问跨域响应除非响应包含Access-Control-Allow-Origin响应标头,否则在 Web 应用程序中运行的 JavaScript 代码

http://catfacts-api.appspot.com/api/facts?number=99没有Access-Control-Allow-Origin响应标头,因此您的前端代码无法访问跨域响应。

您的浏览器可以获得良好的响应,您可以在 Postman 甚至浏览器开发工具中看到它——但这并不意味着浏览器会将它暴露给您的代码。他们不会,因为它没有Access-Control-Allow-Origin响应头。因此,您必须改为使用代理来获取它。

代理向该站点发出请求,获取响应,添加Access-Control-Allow-Origin响应标头和所需的任何其他 CORS 标头,然后将其传递回您的请求代码。并且Access-Control-Allow-Origin添加标头的响应是浏览器看到的,因此浏览器让您的前端代码实际访问响应。


所以我试图将一个对象传递给我的 Fetch,这将禁用 CORS

你不想那样做。需要明确的是,当您说要“禁用 CORS”时,实际上您的意思是要禁用同源策略CORS 本身实际上是一种方法——CORS 是一种放松同源策略的方法,而不是一种限制它的方法。

但是无论如何,您确实可以在本地环境中提供浏览器运行时标志以禁用安全性和不安全地运行,或者您可以在本地安装浏览器扩展程序来绕过同​​源策略,但这只是只为您在本地改变情况。

无论您在本地进行什么更改,任何其他尝试使用您的应用程序的人仍然会遇到同源策略,并且您无法为应用程序的其他用户禁用该策略。

mode: 'no-cors'除非在少数情况下您很可能永远不想在实践中使用,即便如此,前提是您确切地知道自己在做什么以及效果如何。这是因为设置mode: 'no-cors'实际上对浏览器说的是,“在任何情况下都阻止我的前端 JavaScript 代码查看响应正文和标头的内容。” 在大多数情况下,这显然不是您想要的。


至于情况下,您考虑使用mode: 'no-cors',请在答案限制适用于不透明的react是什么?详情。它的要点是:

  • 在有限的情况下,当您使用 JavaScript 将来自另一个来源的内容放入<script>, <link rel=stylesheet>, <img>, <video>, <audio>, <object>, <embed>, 或<iframe>元素时(之所以有效,是因为允许跨源嵌入资源),但出于某种原因,您不这样做不想/不能仅仅通过让文档的标记使用资源 URL 作为元素hreforsrc属性来做到这一点

  • 当您想要对资源做的唯一事情就是缓存它时。正如在哪些限制适用于不透明响应中提到的那样,在实践中的场景是当您使用 Service Workers 时,在这种情况下相关的 API 是Cache Storage API

但即使在这些有限的情况下,也有一些重要的问题需要注意;请参阅哪些限制适用于不透明响应?详情。


我也试过传入对象 { mode: 'opaque'}

没有mode: 'opaque'请求模式——opaque它只是response 的一个属性,浏览器在使用no-cors模式发送的请求的响应上设置该不透明属性

但顺便说一句,不透明这个词是一个非常明确的信号,表明您最终得到的响应的性质:“不透明”意味着您看不到它。

喜欢cors-anywhere简单的非产品用例解决方法(即获取一些公开可用的数据)。这个答案证实了我的怀疑,这no-cors并不常见,因为它的 OpaqueResponse 不是很有用;即“非常有限的情况”;任何人都可以向我解释no-cors有用的例子吗?
2021-03-12 12:50:58
@TheRedPea 在此处查看我对答案所做的更新(并且我还在stackoverflow.com/questions/52569895/...添加了对您的问题的评论
2021-03-17 12:50:58
我需要使用 CORS.js 包配置我的 Express 服务器 - github.com/expressjs/cors - 然后我需要mode: 'no-cors'从获取请求中删除(否则响应将为空)
2021-03-23 12:50:58
“在任何情况下都阻止我的前端 JavaScript 代码查看响应正文和标头的内容。” - 对一个明显无用的“功能”的总结。感谢您如此清楚地拼写出来;我快疯了,试图弄清楚它的用途是什么......
2021-03-25 12:50:58
CORS 代理很棒。我很惊讶它被认为是完全阻止访问公共文件的“安全”措施。从黑客的角度来看,它很容易绕过,而这正是它所做的。对于好演员来说,这真的只是一个麻烦。
2021-04-03 12:50:58

因此,如果您像我一样在本地主机上开发一个网站,尝试从 Laravel API 获取数据并在您的 Vue 前端使用它,并且您看到了这个问题,那么我是如何解决它的:

  1. 在您的 Laravel 项目中,运行 command php artisan make:middleware Cors这将为app/Http/Middleware/Cors.php创建
  2. handles函数中添加以下代码Cors.php

    return $next($request)
        ->header('Access-Control-Allow-Origin', '*')
        ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    
  3. 在 中app/Http/kernel.php,在$routeMiddleware数组中添加以下条目

    ‘cors’ => \App\Http\Middleware\Cors::class
    

    (会有像阵列中的其他项目authguest等等。另外,还要确保你在做这个app/Http/kernel.php,因为还有另外一个kernel.php太Laravel)

  4. 在要允许访问的所有路由的路由注册上添加此中间件,如下所示:

    Route::group(['middleware' => 'cors'], function () {
        Route::get('getData', 'v1\MyController@getData');
        Route::get('getData2', 'v1\MyController@getData2');
    });
    
  5. 在 Vue 前端,请确保在mounted()函数中而不是在data(). 还要确保您呼叫中使用http://https://使用 URL fetch()

全部归功于皮特·休斯顿的博客文章

非常简单的解决方案(配置 2 分钟)是使用local-ssl-proxynpm

用法很简单:
1. 安装包: npm install -g local-ssl-proxy
2. 在运行local-server掩码时使用local-ssl-proxy --source 9001 --target 9000

PS:更换--target 9000-- "number of your port"--source 9001--source "number of your port +1"

@HamidAraghi 我现在想,因为出于开发目的,代理服务器应该在实际受错误影响的设备上运行
2021-03-28 12:50:58
我在真实手机设备上使用我的应用程序时遇到问题。您的解决方案对这种情况有帮助吗?
2021-04-08 12:50:58

如果你使用 Express 作为后端,你只需要安装 cors 并在 app.use(cors()); 中导入和使用它。如果未解决,请尝试切换端口。切换端口后肯定会解决

我记得我的大学四年级课程,除了我们的之外,没有其他团体能做到这一点——奇怪的是,这是一个简单的解决方案。这是一个更全面的指南:stackabuse.com/handling-cors-with-node-js
2021-04-07 12:50:58

我的解决方案是在服务器端做

我使用 C#WebClient库来获取数据(在我的例子中是图像数据)并将其发送回客户端。在您选择的服务器端语言中可能有一些非常相似的东西。

//Server side, api controller

[Route("api/ItemImage/GetItemImageFromURL")]
public IActionResult GetItemImageFromURL([FromQuery] string url)
{
    ItemImage image = new ItemImage();

    using(WebClient client = new WebClient()){

        image.Bytes = client.DownloadData(url);

        return Ok(image);
    }
}

您可以根据自己的用例进行调整。重点是client.DownloadData()没有任何 CORS 错误。通常,CORS 问题仅发生在网站之间,因此可以从您的服务器发出“跨站点”请求。

然后 React fetch 调用很简单:

//React component

fetch(`api/ItemImage/GetItemImageFromURL?url=${imageURL}`, {            
        method: 'GET',
    })
    .then(resp => resp.json() as Promise<ItemImage>)
    .then(imgResponse => {

       // Do more stuff....
    )}