clash for windows 系统代理时 pip 出现 ProxyError 的情况分析记录
22/04/02 Update:
有一位朋友在留言中问了下面的问题:
> cfw中启用Specify Protocol或者pac对访问https网站依旧无效
于是我又打开阔别已久的 Windows 开发机看了一下(顺便一提,果子也挺烂的,尤其是ARM版的,不过看在字体渲染的份上还是先用果子吧):
问题在于 Clash for Windows 的Specify Protocol处理其实还是有问题,如果你之前没有仔细完全阅读下面那个设定代理服务器的函数,翻下去看一下它,就在倒数第二张图。
可以看到如果只是单纯地把注册表项的内容由 `127.0.0.1:7890`改为`http://127.0.0.1:7890`的话,urllib只会返回一个只有一个key也就是`http`的代理dict。
这时候从pip的请求调用链往上找,可以看到负责决定使用这个dict中哪个代理的代码是 `requests/utils.py`的`select_proxy`函数:
![](https://vip1.loli.io/2022/04/02/fj2DLqrsTV1ludp.png)
因为红框中的部分的限制,当你请求`https://pypi.org`的时候,只有key为`https`/`https://pypi.org`/`all`/`all://pypi.org`的代理会被使用,上面那个`http`的代理自然也就不会被使用。
BTW,因为这个代码是 requests 库的,这也就意味着在 Windows 平台上 Clash for Windows 的系统代理不会影响到大部分py应用的http请求。
而正确的做法是什么呢?让我们在IE中设置上代理,然后看一看它给出的行为:
![](https://vip1.loli.io/2022/04/02/HbkhtyWvcsKGeNR.png)
![](https://vip2.loli.io/2022/04/02/AZR7aDWN9wFxyT2.png)
同时,因为 urllib 中还存在代理的类型推测代码,所以正确的设置应该是:
`http=http://127.0.0.1:7890;https=http://127.0.0.1:7891`
21/06/07 Update: CFW [解决了](https://github.com/Fndroid/clash_for_windows_pkg/issues/1787) 这个问题,此问题同样存在于 [QV2ray](https://github.com/Qv2ray/Qv2ray/issues/1533) 22/02/05: 似乎此问题在新版 Python 中的报错信息会变成 `There was a problem confirming the ssl certificate: HTTPSConnection Pool(host='pypi.org', port=443): Max retries exceeded with url: /simple/plotly/ (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1123)')))` 或者类似的错误。 在某书的文章中我看到了类似的问题,报错是 `ValueError: check_hostname requires server_hostname`,虽然我没复现,不知道对应的版本,也没看过对应的代码,不过就逻辑来说应当是相同的问题。
最近终于想写点 Python,结果打开 PyCharm 开个 venv 之后,pip给我疯狂报错: ``` WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple /gitpython/ WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple /gitpython/ ``` 偏偏在venv外面就是一切正常,这促使我想找到问题。 遇到这种问题那肯定是直接debug走起,首先用文本搜索到抛这个错的函数,然后在嫌疑语句上都打上断,我们就能找到罪魁祸首: ![image-20210222200745856](https://vip1.loli.io/2021/02/22/gozhcf7nx6mFp3t.png) 追到这个函数的实现里面,虽然直接看起来没什么问题,但是和venv外没问题的老版pip一比较,很容易就能发现不对劲:选中的那两行在老版是不存在的 ![image-20210222200929044](https://vip2.loli.io/2021/02/22/M3uycJwZl6QOgE1.png) 有了这个额外信息,我们很容易就能找到这个 [Support for web proxies is broken in pip 20.3 · Issue #9190 · pypa/pip (github.com)](https://github.com/pypa/pip/issues/9190) 来说明问题,2016年底,curl加入了这个把https协议前缀另加解释与定义的联盟:[HTTPS proxy with curl | daniel.haxx.se](https://daniel.haxx.se/blog/2016/11/26/https-proxy-with-curl/),而 urllib3 显然也跟上了这个脚步: ![image-20210222202632913](https://vip2.loli.io/2021/02/22/lRnUMIEw2kBVL7f.png) 在因为前缀设置了 `tls_in_tls_required` 之后,urllib3 会企图把这个代理服务器看作一个套了 tls 的 http CONNECT 代理。 但是问题是,我并没有设置 `https://` 前缀的代理服务器,这个行为是什么神奇的情况呢? 继续向下追,找到如何获取代理的: ![image-20210222201304250](https://vip2.loli.io/2021/02/22/vlOdsYbQ4FGKRfr.png) 确实,env里面没有proxy,那按照windows的习俗找找注册表也情有可原对吧? 这个函数的内部实现是这样: ![image-20210222201432723](https://vip2.loli.io/2021/02/22/ZgJR9yecEb6tKOk.png) 看红框的行为是不是好像很眼熟?IE的代理设置似乎就是这样的? 不,并不是一样的,因为IE的代理设置把HTTPS(在`zh-MS`方言里叫安全)代理定义为支持 `CONNECT` 动词的HTTP代理,尽管很久以来人们都是这样用的,但是当它前面出现一个协议前缀的时候就不一样了。 因为 clash for windows 打开系统代理的代理配置看起来并没有写明了 protocol: ![image-20210222222519422](https://vip2.loli.io/2021/02/22/P8sMyutm2LVGKbf.png) 所以首先,我们的py会根据IE时代的约定俗成把这样一个没有指明protocol的proxy url自动补全三种协议,然后再按照约定俗成的行为为https请求使用https_proxy,最后在一个http代理上试图开tls。 这个配置在 IE 时代行为会是正常的,在现代的库中行为也是正常的,但是对于这样一个混杂了两种行为的库,模糊不清就成了问题。 上面的截图并不是 urllib3 的,而是 属于py自己的标准库 urllib 的,也算是 urllib3 开发人员的思想和 urllib 的历史遗留实现冲突了吧。
21/06/07 Update: CFW [解决了](https://github.com/Fndroid/clash_for_windows_pkg/issues/1787) 这个问题,此问题同样存在于 [QV2ray](https://github.com/Qv2ray/Qv2ray/issues/1533) 22/02/05: 似乎此问题在新版 Python 中的报错信息会变成 `There was a problem confirming the ssl certificate: HTTPSConnection Pool(host='pypi.org', port=443): Max retries exceeded with url: /simple/plotly/ (Caused by SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1123)')))` 或者类似的错误。 在某书的文章中我看到了类似的问题,报错是 `ValueError: check_hostname requires server_hostname`,虽然我没复现,不知道对应的版本,也没看过对应的代码,不过就逻辑来说应当是相同的问题。
最近终于想写点 Python,结果打开 PyCharm 开个 venv 之后,pip给我疯狂报错: ``` WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple /gitpython/ WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple /gitpython/ ``` 偏偏在venv外面就是一切正常,这促使我想找到问题。 遇到这种问题那肯定是直接debug走起,首先用文本搜索到抛这个错的函数,然后在嫌疑语句上都打上断,我们就能找到罪魁祸首: ![image-20210222200745856](https://vip1.loli.io/2021/02/22/gozhcf7nx6mFp3t.png) 追到这个函数的实现里面,虽然直接看起来没什么问题,但是和venv外没问题的老版pip一比较,很容易就能发现不对劲:选中的那两行在老版是不存在的 ![image-20210222200929044](https://vip2.loli.io/2021/02/22/M3uycJwZl6QOgE1.png) 有了这个额外信息,我们很容易就能找到这个 [Support for web proxies is broken in pip 20.3 · Issue #9190 · pypa/pip (github.com)](https://github.com/pypa/pip/issues/9190) 来说明问题,2016年底,curl加入了这个把https协议前缀另加解释与定义的联盟:[HTTPS proxy with curl | daniel.haxx.se](https://daniel.haxx.se/blog/2016/11/26/https-proxy-with-curl/),而 urllib3 显然也跟上了这个脚步: ![image-20210222202632913](https://vip2.loli.io/2021/02/22/lRnUMIEw2kBVL7f.png) 在因为前缀设置了 `tls_in_tls_required` 之后,urllib3 会企图把这个代理服务器看作一个套了 tls 的 http CONNECT 代理。 但是问题是,我并没有设置 `https://` 前缀的代理服务器,这个行为是什么神奇的情况呢? 继续向下追,找到如何获取代理的: ![image-20210222201304250](https://vip2.loli.io/2021/02/22/vlOdsYbQ4FGKRfr.png) 确实,env里面没有proxy,那按照windows的习俗找找注册表也情有可原对吧? 这个函数的内部实现是这样: ![image-20210222201432723](https://vip2.loli.io/2021/02/22/ZgJR9yecEb6tKOk.png) 看红框的行为是不是好像很眼熟?IE的代理设置似乎就是这样的? 不,并不是一样的,因为IE的代理设置把HTTPS(在`zh-MS`方言里叫安全)代理定义为支持 `CONNECT` 动词的HTTP代理,尽管很久以来人们都是这样用的,但是当它前面出现一个协议前缀的时候就不一样了。 因为 clash for windows 打开系统代理的代理配置看起来并没有写明了 protocol: ![image-20210222222519422](https://vip2.loli.io/2021/02/22/P8sMyutm2LVGKbf.png) 所以首先,我们的py会根据IE时代的约定俗成把这样一个没有指明protocol的proxy url自动补全三种协议,然后再按照约定俗成的行为为https请求使用https_proxy,最后在一个http代理上试图开tls。 这个配置在 IE 时代行为会是正常的,在现代的库中行为也是正常的,但是对于这样一个混杂了两种行为的库,模糊不清就成了问题。 上面的截图并不是 urllib3 的,而是 属于py自己的标准库 urllib 的,也算是 urllib3 开发人员的思想和 urllib 的历史遗留实现冲突了吧。
此评论已被作者删除。
回复删除原来如此,但这也就是说clash的代理,是不支持https吗?按照网上的教程,平常手动加代理,不也是加两个http_proxy和https_proxy变量吗。但是指向的不都是一个代理ip吗。这块不是很明白。
回复删除https_proxy 指在访问 https 协议的 url 时使用的代理,可以是 http/https/socks 等协议的代理,通常也是以uri的形式指明协议
删除碰到这个问题很久了,每次都只能暂时把clash系统代理关闭,今天终于知道原因了。蛋疼的是这问题双方各自都没有bug,估计不可能官方修了。。所以说这个有什么补救的方法吗?
回复删除clash新版本有个Specify Protocol开启就好了,或者System Proxy的Type换成PAC。都可以解决
删除嗯,刚刚看了cfw的issue已经解决了,还是谢谢了
删除非常感谢
删除我开了specify protocol或换成pac会报另外的错 ERROR: XMLRPC request failed [code: -32500],还是不不行
回复删除Nice 找到方法了
回复删除感谢,clash 新版本有个 Specify Protocol 开启就好了,已经好了
回复删除感谢感谢
回复删除cfw中启用Specify Protocol或者pac对访问https网站依旧无效
回复删除启用Specify Protocol会导致只设置http代理,https无代理
系统代理获取,python3.9.7:
import urllib
urllib.request.getproxies()
这是一个很有趣的问题,我已经将这个问题的分析更新在文章的最上面
删除太傻卵了这个pip开发组
回复删除请问一下大牛,为什么Clash4windows开了Tun模式对Anaconda Prompt没有起效呢,还是必须要给anaconda配置那个http proxy和Https proxy,对这块不是很了解,我对Tun模式的理解是虚拟了一个新的网卡,理论上讲Anaconda Prompt应该走虚拟网卡走流量啊
回复删除解决了我一个大问题,代码总是提示EOF occurred in violation of protocol (_ssl.c:841),clash的System Proxy的Type换成PAC。完美解决
回复删除