这个问题应该确确实实是一个 Chrome 的 BUG,我在自己的编程环境中发现,并在多个服务器,多个编程语言的运行环境,以及多个浏览器下都测试过,都看到有 2 次请求出现。为了证明不是自己环境的问题,我也特意去找了一些其他站点,用它里面的一些会产生重定向的请求来测试。比如这个请求地址,这是 golaravel 问答模块进行 qq 登录的链接地址,它会重定向到 qq 授权登录的页面。
使用 fiddler 监控 wenda.golaravel.com 这个站点,然后在 Chrome 里面访问以上地址,回到 fiddler 查看监控结果:
从这个结果可以明确地看到,Chrome 对这个地址发出了 2 次 http 请求,并且这两次 http 请求都从服务器拿到了 Response。从 fiddler 的 Statistics 栏里面,可以对比看出这两个请求过程的一些信息:
第一个是:
第二个是:
从这个对比结果可以看到,这两个请求并不是一种串行的关系,而是并行的关系。但是这个问题的特性还不止这么简单,目前我发现的一些现象如下:
1)不是每次都会出现这个情况,我只能说大部分测试操作都出现这种情况,但是少部分情况是正常的:访问 302 的地址,对原地址只发起了一次请求,第二次是对 302 Location 指定的地址的请求,这是正常的;
2)这两个请求并不是每次都能成功从服务器拿到响应,有的时候其中的一个会报 500 的错误,或者是显示 failed,此时如果观察 fiddler 的 log 记录,会看到有下面的一些关键信息:
- Session #143 raised exception System.Net.Sockets.SocketException
- //or
- This request was retried after a Receive operation failed.
3)大部分情况下,都是这两个请求先发起,然后再对 302 Location 重定向请求;但是极少的情况会遇到 302 重定向请求夹在这两个请求中间。
目前我并没有找到这个问题产生的原因,网上相关的资料太少了,所以我只能从多个方面来验证是否是 Chrome 本身的问题,最后得出的结论也跟我的猜想一致,这就是 Chrome 较新版本的一个 BUG,我在当前时间点的最新版本的 Chrome 浏览器里面看到这个现象的:
下面我会详细地证明它是否为 Chrome 的一个 BUG。
1. 问题的发现
我使用 laravel 框架写了一个很简单的程序,就几个页面,做微信登录的 demo 用的,所以页面里的微信登录按钮,实际上就是一个需要重定向到微信授权页面的地址。我在浏览器里面访问这个地址的时候,在本地 windows 下的 apache 服务器的 access.log 里面,发现这一个访问操作产生了三次 http 请求:
从这个日志里很明显地看到有 2 次对 / login/weixin 的请求,这个就是重定向请求的原请求记录。发现这个问题之后,我并不认为是服务器或者浏览器的问题,而是首先怀疑是否是自己代码里面有重定向 loop 的情况。所以第一步就去仔细地检查自己的代码:
- /**
- * 微信登录
- * @param Request $request
- * @return mixed
- */
- public function weixin(Request $request)
- {
- $q_str = $request->getQueryString();
- return Socialite::with('weixin')
- //设置授权的回调地址,通过命名路由的形式
- ->setRedirectUrl(route('notify_weixin') . ($q_str ? '?' . $q_str : ''))
- ->redirect();
- }
最后的 redirect 方法是:
- /**
- * 重定向并将state参数写到cookie里面去,而不是采用session
- * @return string
- */
- public function redirect()
- {
- $state = $this->getState();
- $response = new RedirectResponse($this->getAuthUrl($state), 302, [
- 'Set-Cookie' => implode('', [
- $this->state_cookie_name,
- '=',
- $this->getEncryptState($state),
- "; path=/; domain=",
- $this->getDomain(),
- "; expires=" . gmstrftime("%A, %d-%b-%Y %H:%M:%S GMT", time() + $this->state_cookie_time),
- "; Max-Age=" . $this->state_cookie_time,
- "; httponly"
- ])
- ]);
- return $response;
- }
这个代码里的 RedirectResponse 是 laravel 框架提供的类。从这个代码,我并没有看出什么问题,因为没有多次创建 Response 的处理。所以开始怀疑是否是新版的 laravel 框架的问题,因为我用的是最新的 laravel 框架。所以我又用公司的 php 环境测试了一下,公司的 php 环境用的 yii 这个 php 框架,结果发现,公司的环境在本地依然存在这个问题。
为了排除是框架的问题,我又自己写了两个最简单的 php 页面:
我把 demo1 部署到 demo1.com,demo2 部署到 demo2.com,然后访问 demo1.com 来测试。最后也还是遇到了这个问题。在这一次测试里面,我还发现了有两个关键点:
1)如果把 302 改成 301 重定向,那么 chrome 就不会产生两次请求。但是实际上 301 用在这里是不对的,因为它的含义是原地址的资源已经永久转移到别的位置了。
2)这个问题导致的两次对原地址的请求,大部分情况下,服务器都能收到并进行处理。(demo1 里面的打印信息,在这个问题出现的时候,每次都打印两个时间信息,说明服务器响应了 2 次)。这意味着用户的一次访问操作,浏览器发出了 2 个请求,服务器对同一个用户操作进行了 2 次处理。这并不是个没有影响的事,比如当这个请求对某个资源的状态做了一些持续性的改变时,如数据累计,那就意味着用户一次操作,就会累计 2 次,这样这些数据结果可能就有问题了。这也是我把这个问题详细记录说明的主要原因,我觉得开发中应该注意到这个问题的存在,以便在排除一些疑难的数据问题的时候,可以思考到这个层面上来。
到此为止,基本上已经排除是 php 框架的问题了。接下来考虑的问题产生对象主要是编程语言,服务器以及浏览器。
2. 排查是否为编程语言的问题
为了验证是否为 php 语言本身的问题,我又用 express 框架写了以下简单代码,并运行在 node 的环境里面来测试:
- app.get('/',
- function(req, res) {
- res.redirect('/redirect');
- });
- app.get('/redirect',
- function(req, res) {
- res.send('success');
- });
最后测试发现这个问题,在 Nodejs 里面同样存在。所以也可以排除是 php 语言的问题了。
3. 排查是否为服务器的问题
因为这个问题本身是在本地 windows 服务器里面发现的,所以我怀疑是否为本机 apache 的问题,所以我又把相关的代码部署到远程的 nginx 服务器上,最后同样测试到这个问题的存在。
加上上一步在 nodejs 里面测试的时候,实际上是用本机的 node 服务器运行的,综合这两个服务器测试结果,也能证明并不是 apache 服务器的问题。
4. 排查是否为浏览器的问题
我的机子上有 360 浏览器,firefox,IE11,Edge 浏览器和最新的 Chrome 浏览器的。最后测试发现只有 Chrome 浏览器里面有这个问题,其它浏览器测试,在 fiddler 里面都只能看到对原地址仅发起了一次 http 请求,在每个浏览器我都重复做了大概十多次访问操作才得出这个结论。为了进一步验证 Chrome 的版本问题,我又特意删了当前版本,下载了一个 Chrome46 的浏览器测试,结果没有发现这个问题。到此为止,也就基本上可以认为是 Chrome 浏览器的问题。
5. 进一步排查是否为本机运行环境的问题
为了进一步排查是自己开发环境的问题,我专门到网上找了一些其他站点的 302 请求地址做测试。除了本文开头提供的地址,下面这个地址也可以测试:
最后,根据以上的排查内容,可以认定这个问题是 Chrome 的一个 BUG,我已经 report 给他们了,回不回复不重要,最重要的是下一个版本这个问题是否能够解决。所以接下来这个问题,我的处理方式是:跟进 chrome 的版本更新情况,并在新版本中进行测试。希望它能在后续的版本中得到解决。
其实在以上排查过程中,还有一些情况值的考虑,比如操作系统环境,网络环境的影响,毕竟 http 请求本身处于这两个大的因素之下,所以也不能完全排除它们的原因。所以,要是感兴趣的朋友,觉得这个问题值得研究的话,非常希望你能把自己的测试结果反馈过来,如果这个问题在你的机器上也存在,那么就能增加本文的客观性和正确性,就能帮助更多的人;如果很多人都没有测试出这个问题,就说明本文的结论有误,这篇文章应该作废才行。
来源: