WAMP Server无法使用PhpMyAdmin的问题与解决

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

问题描述

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

http://localhost

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

http://localhost/phpmyadmin

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

http://localhost/phpmyadmin/index.php

排查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的配置文件应该也没有问题。

在开发者工具中的发现

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

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

开发者工具中的错误信息

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

在Apache日志中的发现

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

access.log

这一行代表的请求在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这么一个路径。脚本运行后,浏览器可以正常访问此路径,如图:

http://localhost/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,也就是OpenWrt使用的那款。

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

http://localhost

加载十分的缓慢,而且右侧的图片没有加载出来。查看开发者工具,还是熟悉的“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根目录时,忽然有了一个新的发现:

Lighttpd的Web根目录

这里的light_button.png是左侧的小图像(可以显示),light_logo.png是右侧的大图像(无法显示)。值得注意的是,小图像的大小为3KB,而大图像的大小为35KB。我突发奇想,会不会是大文件就会请求失败呢?(后记:这个问题其实观察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根目录,用浏览器访问,结果如下:

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

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

找到问题根源

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

FDM设置

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

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

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

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

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

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

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