clash for windows 系统代理时 pip 出现 ProxyError 的情况分析记录

Created
Aug 3, 2022 04:00 PM
Tags
Proxy
URL
有一位朋友在留言中问了下面的问题: > 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.pyselect_proxy函数:
notion image
因为红框中的部分的限制,当你请求https://pypi.org的时候,只有key为https/https://pypi.org/all/all://pypi.org的代理会被使用,上面那个http的代理自然也就不会被使用。 BTW,因为这个代码是requests库的,这也就意味着在Windows平台上Clash for Windows的系统代理不会影响到大部分py应用的http请求。 而正确的做法是什么呢?让我们在IE中设置上代理,然后看一看它给出的行为:
notion image
notion image
同时,因为urllib中还存在代理的类型推测代码,所以正确的设置应该是:http=http://127.0.0.1:7890;https=http://127.0.0.1:7891
21/06/07 Update: CFW 解决了 这个问题,此问题同样存在于 QV2ray 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走起,首先用文本搜索到抛这个错的函数,然后在嫌疑语句上都打上断,我们就能找到罪魁祸首:
notion image
追到这个函数的实现里面,虽然直接看起来没什么问题,但是和venv外没问题的老版pip一比较,很容易就能发现不对劲:选中的那两行在老版是不存在的
notion image
有了这个额外信息,我们很容易就能找到这个 Support for web proxies is broken in pip 20.3 · Issue #9190 · pypa/pip (github.com) 来说明问题,2016年底,curl加入了这个把https协议前缀另加解释与定义的联盟:HTTPS proxy with curl | daniel.haxx.se,而urllib3显然也跟上了这个脚步:
notion image
在因为前缀设置了 tls_in_tls_required 之后,urllib3会企图把这个代理服务器看作一个套了tls的http CONNECT代理。 但是问题是,我并没有设置 https:// 前缀的代理服务器,这个行为是什么神奇的情况呢? 继续向下追,找到如何获取代理的:
notion image
确实,env里面没有proxy,那按照windows的习俗找找注册表也情有可原对吧? 这个函数的内部实现是这样:
notion image
看红框的行为是不是好像很眼熟?IE的代理设置似乎就是这样的? 不,并不是一样的,因为IE的代理设置把HTTPS(在zh-MS方言里叫安全)代理定义为支持 CONNECT 动词的HTTP代理,尽管很久以来人们都是这样用的,但是当它前面出现一个协议前缀的时候就不一样了。 因为clash for windows打开系统代理的代理配置看起来并没有写明了protocol:
notion image
所以首先,我们的py会根据IE时代的约定俗成把这样一个没有指明protocol的proxy url自动补全三种协议,然后再按照约定俗成的行为为https请求使用https_proxy,最后在一个http代理上试图开tls。 这个配置在IE时代行为会是正常的,在现代的库中行为也是正常的,但是对于这样一个混杂了两种行为的库,模糊不清就成了问题。 上面的截图并不是urllib3的,而是 属于py自己的标准库urllib的,也算是urllib3开发人员的思想和urllib的历史遗留实现冲突了吧。