phpMyAdmin无法访问的排查记录

这几天在 Windows 上使用 WAMP Server 环境时,遇到了离奇的 phpMyAdmin 无法使用的问题。为了解决这个问题,我做了各方面的尝试,花了将近两天的时间,最终”灵光一现“,在一个很不起眼的地方找到了问题的根源。虽然我遇到的问题具有很强的特殊性,遇到的概率很小,但是整个探索的过程十分的波折而有趣,特此记录。另外本文内容的先后顺序严格遵循时间顺序,因此可能会有些凌乱,还请见谅!如果只想看结论,可以直接跳到文末。

问题描述

在 Chrome 中访问 http://localhost,可以正常访问,如下图:

但是访问 http://localhost/phpmyadmin 时,会加载很长时间,然后显示一个加载不完全的界面,如下图:

尝试使用正确的账户密码登录,在按下 Go 按钮之后,又会加载很长时间,然后提示 ”The connection was reset.“,如下图:

排查WAMP Server的问题

一开始,我的直觉是,一定是 WAMP Server 的某个地方出了问题,重装应该可以解决这个问题。于是,我重新安装了 WAMP Server,但是问题依然存在。

排查浏览器的问题

在网上搜索了我所遇到的情况后,我在 WAMP Server 官方论坛找到了一个帖子。帖子中描述的情况是:“ERR_CONNECTION_RESET” 错误,和 phpMyAdmin 登录界面上出现的如下错误信息:

There is mismatch between HTTPS indicated on the server and client. This can lead to non working phpMyAdmin or a security risk. Please fix your server configuration to indicate HTTPS properly.

这和我的情况完全符合。

我浏览了这个帖子,得到的一个解释是:

Try using a browser other than Chrome, see if it works with that!

Hi,

> The error message is “There is mismatch between HTTPS indicated on the server and client.

phpMyAdmin from Wampserver is not launched as HTTPS : ‘http://localhost/phpmyadmin/'

It seems that more and more Chrome (Google) wants to behave like a web boss and wants to force the use of https, in the same way that it hijacked the tld “.dev”

Supposedly for our safety, but ultimately to be the Internet dictator.

翻译一下,就是说 Google 在 HTTPS 上十分的执着且霸道,因此 Chrome 会阻断一些 HTTP 连接。虽然感觉这个解释并不靠谱,但是我仍然抱着试一试的态度,试用了不同的浏览器:

  • Edge Chromium - 问题依然存在
  • Internet Explorer - 问题依然存在
  • Mozilla Firefox - 问题依然存在

新版 Edge 用的是 Chromium 引擎,和 Chrome 是近邻,它出问题不能说明什么。但是 IE 和 Firefox 也有同样的问题,足以证明这个问题并非浏览器导致。那么浏览器的嫌疑被排除了。

排查Xdebug的问题

帖子中另一种说法认为这个问题是 PHP 的 Xdebug 组件导致的。我尝试禁用了 php_opcache 和 php_xdebug 两个组件,问题依然存在。更何况,之前使用 WAMP Server 时,这两个组件都是默认开启的,但我并未遇到 phpMyAdmin 访问问题。所以排除 Xdebug 的嫌疑。

一个不完美的解决方案

帖子的最后,问题通过更改 Apache 端口的方式解决了。我尝试将 Apache 的监听端口从 80 改为了 8080,发现 phpMyAdmin 可以正常访问了。但是这个解决方式是不完美的。80 端口上的问题依旧存在,更改端口只是避开了问题罢了,并没有真正解决它。

为了找到问题根源,我又在网上搜索了很久,可惜并没有找到更多的有实质性帮助的信息。我于是开始了一个人的离线琢磨。

在WAMP Server中的测试

我试着在 WAMP Server 的 www 目录中新建了一个 halo 文件夹,在其中新建了 index.htmlindex.php 两个文件。其中 index.html 的内容是一行文字,而 index.php 则是 echo() 了一行文字。经测试,两个文件都能正常访问。但是当我进一步把 index.php 的内容修改为:

<?php
phpinfo();

这时浏览器就无法访问 index.php 了,症状为一直加载不出来,最后显示 ”ERR_CONNECTION_RESET“,和 phpMyAdmin 那边的状况如出一辙。难道是 PHP 解释器出了问题?显然不是,因为一开始 index.php 是能够被正常解释的。

排查Apache配置的问题

虽然说 WAMP Server 已经重新安装过了,但是 Apache 错综复杂的配置文件给了我一种问题就在其中的错觉。既然 80 端口不能正常访问,而 8080 端口没有任何问题,会不会是配置文件导致的呢?我搜索了各个配置文件,发现唯一提到了 80 端口的地方,就是 Apache 自带的 localhost 虚拟主机的那段配置。我试着注释了这段配置,但是问题依然存在,所以 Apache 的配置文件应该也没有问题。

在开发者工具中的发现

鉴于无头苍蝇式的尝试都没有结果,我意识到,我很可能遇到了一个极其特殊的案例。如果继续随意地修改配置信息,不但解决不了眼前的问题,还很可能不小心把 WAMP 环境整崩。

我于是开始重新整理排查问题的思路。既然问题表现在网页上,那为何不仔细看看网页上发生了什么呢?在 phpMyAdmin 登录界面按下 F12 打开开发者工具——出现了满屏的错误信息!

仔细看了看这些错误信息,发现关键原因在于部分 JS 和 CSS 文件请求失败,这也解释了为什么网页的排版很不正常。而请求的失败信息,正是之前出现过的 “ERR_CONNECTION_RESET”。当我换成 8080 端口访问时,则没有任何错误信息出现。

在Apache日志中的发现

除了查看开发者工具之外,我也很自然地想到了检查 Apache 的日志。

这一行代表的请求在 Chrome 的开发者工具中是失败的,原因为 “ERR_CONNECTION_RESET“,但是从 Apache 的角度来看,服务器返回的状态码为 200(OK),也就是说这个请求是成功的。也就是说,虽然服务器成功响应了这个请求,但是这个请求并没有被浏览器接收到。换句话说,如果服务器的日志和浏览器的开发者工具没有骗我,这个请求就是在半路上被拦截了。那么是谁拦截了这个请求呢?为什么还是有一小部分请求是正常的呢?难不成这个拦截还是有一定规律的?

在Flask中的测试

为了进一步排除 Apache 作为服务器的嫌疑,我想到可以用别的 HTTP 服务器来代替 Apache,再测试 80 端口的连通性。Python Flask 可以作为一个简易的 Web 服务器,于是我编写了如下的代码:

from flask import Flask

app = Flask(__name__,
            static_url_path='',
            static_folder='www')


@app.route('/', methods=['GET'])
def test_page():
    return "It works!"


@app.route('/phpmyadmin/js/config.js', methods=['GET'])
def test_page_2():
    return "Okay, though it's not a file!"


if __name__ == '__main__':
    app.run(
        host='127.0.0.1',
        port=80
    )

由于 www 文件夹被作为了 Flask 的静态资源文件夹,我在其中创建了含有一行文字的 index.html,用浏览器访问,一切正常!

从之前开发者工具的输出中,可以看到 /phpmyadmin/js/config.js?v=4.9.2 这个请求是失败的。因为问号之后的内容是参数,因此我在Flask中定义了 /phpmyadmin/js/config.js 这么一个路径。脚本运行后,浏览器可以正常访问此路径,如图:

也就是说,如果问题是半路上的”拦截“导致的,在这个实验中所谓的”拦截“并没有发生,或者说这个”拦截“的规则并不是基于 URL 路径的(因为之前被拦截的路径能够正常访问了)。但是这个实验并不能很好的缩小问题位置的范围,Apache 依然有嫌疑(毕竟切换到 Flask 问题就消失了)。

在PhpStudy中的测试

由于刚刚的实验并没有为 Apache 洗清嫌疑,我想到了用其他的 WAMP/WNMP 框架来测试(自己逐个安装组件也可以,但是太麻烦了)。我尝试了 PhpStudy 中的 WAMP,安装好 phpMyAdmin,在 Chrome 中访问,问题依旧。秉着严谨的态度,我又切换到 WNMP,在 Chrome 中访问,问题依旧。

通过这个实验,我可以确信,问题并不出在 WAMP Server 上,因为在 PhpStudy 中我也遇到了相同的问题。况且 WAMP Servre 和 PhpStudy 都是主打随装随用的,理论上无需配置即可正常工作;它们两者都有着不小的用户群体,两个软件同时出现一个 BUG 的概率,很低很低。

在之前的实验中,我已经排除了浏览器的嫌疑;现在又排除了 HTTP 服务器的嫌疑。总结一下,现在可以高度确信的是:问题出在了服务器-浏览器之间,也就是部分请求被半路拦截了。

排查防火墙的问题

由于防火墙一直是 Windows 的传统艺能,仿佛条件反射一般,遇到拦截有关的问题都应该首先尝试关闭防火墙或检查防火墙设置。我尝试关闭了防火墙,又尝试了重置防火墙设置,但都不能解决问题。所以本次问题与防火墙无关。

排查winsock和DNS缓存的问题

虽然我并不是很懂 winsock 到底是什么,但是确实很多问题确实可以通过重置 winsock 或刷新 DNS 缓存来解决。于是我尝试运行了一下命令:

netsh winsock reset
ipconfig /flushdns

重启电脑,问题依然存在,说明本次问题与 winsock 和 DNS 缓存无关。

在Lighttpd中的测试、在手机上的发现

虽然通过之前实验,我已经可以确定问题出在服务器-浏览器之间,但是我依然不死心地想要尝试其他的 HTTP 服务器。这次我选用的是轻量级的 Lighttpd。

启动 Lighttpd 后,我尝试使用浏览器访问 http://localhost,得到的是 Lighttpd 的测试页面,如图:

加载十分的缓慢,而且右侧的图片没有加载出来。查看开发者工具,还是熟悉的 “ERR_CONNECTION_RESET” 错误。

因为 Lighttpd 默认监听地址为 0.0.0.0,我之前又关闭了防火墙,我便顺手尝试了一下用手机访问电脑,结果一切正常,图片也能显示出来,如图:

这就很奇怪:电脑本地访问有问题,用手机远程访问反而没有问题了。基本可以确定,这个问题不光是在服务器-浏览器之间,而且更偏向浏览器一些。或者,会不会是电脑上的 Loopback Adapter 出了问题呢?

排查Loopback Adapter的问题

检查了电脑上现有的网络适配器,我发现有几个可疑的对象,一是网络类软件经常使用的 NpLoopbackAdapter,二是 VMWare 创建的给虚拟机上网用的几个适配器。抱着“宁可错杀三千。不可放过一个”的想法,我卸载了 NpLoopbackAdapter 和 VMWare。这样就没有可疑的适配器了,然而就算重启后,问题依然存在。这说明 NpLoopbackAdapter 和 VMWare 的适配器都是 OK 的。

排查系统的问题

很早之前,我遇到过一个某个网站无法访问的玄学问题,各种方法试遍都不能解决,最后不得不重装了系统。那会不会这次问题的原因是系统文件损坏呢?运行如下命令检查系统文件:

sfc /scannow

扫描结果显示:系统文件没有损坏。虽然不排除存在暗病的可能性,但是可以确定的是系统出问题的概率很低,剩下的唯一一种可能性,就是有某个软件拦截了请求。

在安全模式中的发现

为了验证这个问题是某个软件拦截请求导致的,我想到了安全模式。安全模式下除系统必需组件外的服务都不会自启动,可以实现一个较为纯净的环境。在安全模式下,我启动了 Lighttpd 并用浏览器访问之,结果如下图:

可以看到,在安全模式下,右侧图像能够被正确地加载,拦截的问题消失了。这个实验既证明了我的问题是由软件拦截请求导致的,也证明了系统文件并未损坏。

排查软件的问题

回到了正常模式,问题依旧。我尝试了挨个禁用服务、挨个停止进程,把能停止的进程都停止了,问题也没有解决。看来这个罪魁祸首藏得很深。

在Lighttpd的Web根目录中的发现

走投无路的我在浏览 Lighttpd 的 Web 根目录时,忽然有了一个新的发现:

这里的 light_button.png 是左侧的小图像(可以显示),light_logo.png 是右侧的大图像(无法显示)。值得注意的是,小图像的大小为 3 KB,而大图像的大小为 35 KB。我突发奇想,会不会是大文件就会请求失败呢?(后记:这个问题其实观察 Apache 的日志也能回答。)

我立刻写了如下的代码:

from random import randint


def generate_file(filename: str, size: int) -> None:
    with open(filename, 'w') as fs:
        for i in range(size):
            fs.write(chr(randint(33, 126)))


if __name__ == '__main__':
    generate_file('2KB.txt', 2 * 1024)
    generate_file('4KB.txt', 4 * 1024)
    generate_file('8KB.txt', 8 * 1024)
    generate_file('34KB.txt', 34 * 1024)

用这段代码,我生成了几个不同大小的 TXT。将其复制到 Lighttpd 的 Web 根目录,用浏览器访问,结果如下:

  • 2 KB - 请求成功
  • 4 KB - 请求成功
  • 8 KB - 请求成功
  • 34 KB - 请求失败

这里的结果验证了刚才的猜想,即过大的文件就会请求失败。也就是说,这次拦截请求的软件,是基于响应大小来拦截的。

找到问题根源

刚刚的结论,其实已经很能说明问题了。经过短时间的痛苦思考,我想到了我的下载软件,Free Download Manager(也就是贫民版 IDM)。我检查了它的设置,发现了这个:

不知道为什么,它们都被勾上了,我试着取消了所有的勾选,发现问题居然就解决了。Lighttpd 的测试页中的图片可以正常显示了,切换回 WAMP Server,phpMyAdmin 也可以正常访问了。

这时候去解释之前遇到的情况,发现一切都解释得通了:在勾选了所有的浏览器后,FDM 会尝试从所有的浏览器捕捉下载,然后那些较大的 JS 和 CSS 文件也被不幸捕捉了,然而 FDM 却没有很好的处理这些请求,于是浏览器在超时之后得出了 “ERR_CONNECTION_RESET” 的结论。

为什么 phpinfo() 无法显示?因为它的响应太大了,会被 FDM 拦截。

为什么所有浏览器都有问题?经实测,这个问题有且仅有在 Edge 被勾选的情况下发生。大胆推测 FDM 可能有 BUG,在尝试捕捉 Edge 的下载时便会错误地拦截所有浏览器的请求……

为什么换端口就可以?可能 FDM 只捕捉 80 端口的请求。

所以,最终的解决方案,就是在 FDM 中取消勾选 Edge

困扰了我将近两天的问题,就这样解决了。