Python3 socket通过代理访问web服务实现
一、说明
1.1 背景说明
关于“代理”,从burpsuite到ss这类正向代理,再从nginx到haproxy这类反向代理,也用了好多年配置了好多年了。在日积月累之下也确认了以下几个问题:
正向代理和反向代理的区别----正常访问路径是client----public network----server,如果代理服务器处于client和public network之间就是正向代理,如果代理服务器处于public network和server之间就是反向代理。
代理从连接的角度是怎么实现的----A建立一个tcp连接到B发送应用层内容,B另建立一个tcp连接到C转发A应用层的内容。
socks5是什么和socket库有什关系和区别----socket我们可以认为是“数据链路层+网络层+传输层”的实现库,socks5是传输层之上的一种协议(类比ssl层)
ssl密钥协商过程是怎样的----SSL密钥协商过程分析
怎么编写代码实现ssl通信----Python3+ssl实现加密通信
但是仍是有一种不甚了解的感觉,比如这两天实现socket使用代理访问web服务代码就又费了好多功夫。
1.2 环境说明
代理服务----windows本地运行的whistle代理服务,监听端口8899
实现语言----python3
实现功能----socket通过代理访问http服务(第二大点)、socket通过代理访问https服务(第三大点)
二、socket实现通过代理访问http服务
2.1 实现代码
import socket # 以http请求www.baidu.com的请求包内容 # http以换行表示请求头完结,所以最后的空行千万不要删掉 http_layer_data = """ GET / HTTP/1.1 Host: www.baidu.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 """ # 第一步,和代理服务器建立连接 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1', 8899)) # 第二步,向代理服务器发送请求 # 代理服务器识别接收到是http协议内容后,会根据Host头进行转发 s.sendall(http_layer_data.encode()) # 第三步,接收到服务器响应并打印 response_data = s.recv(1024) print(response_data.decode()) s.close()
2.2 响应结果
如下图所示,成功接收到302重定向响应
三、socket实现通过代理访问https服务
现在大多数服务都不再使用http协议,而是转而使用https协议。
如果是代码直接访问https服务,那问题还是比较明朗的,我们只要先与https服务端建立一个ssl连接,然后再发送http层内容即可。
但是如果中间插入一个代理,这问题就比较棘手:客户端该用socket和代理服务器连接呢还是该用ssl和代理服务器连接、客户端又该如何通知代理服务器使用https向https服务端转发内容。
3.1 代码实现
import socket import ssl # 以http请求www.baidu.com的请求包内容 # http以换行表示请求头完结,所以最后的空行千万不要删掉 http_layer_data = """ GET / HTTP/1.1 Host: www.baidu.com Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 """ # 第一步,和代理服务器建立socket连接 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 8899)) # 第二步,代理服务器与https服务端进行密钥协商并建立ssl连接,用于后续转发 connect_request_http_layer_data = """ CONNECT www.baidu.com:443 HTTP/1.0 Connection: close """ s.sendall(connect_request_http_layer_data.encode()) response_data = s.recv(1024) print(response_data.decode()) # 第三步,建立ssl连接 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ss = context.wrap_socket(s) # 第四步,向代理服务器发送请求 # 代理服务器会解出其中的http层内容,使用先前和https服务端建立的连接,进行转发 ss.send(http_layer_data.encode()) # 第五步,接收到服务器响应并打印 response_data = ss.recv(1024) print(response_data.decode()) ss.close()
3.2 响应结果
四、不建议使用socket访问应用层服务的原因
不建议使用socket访问应用层服务的原因,其实除了代理这种功能比较费劲外,还有一个更主要的原因。
在上边的代码中我们发送一个请求然后接收响应,好像一切都正常没什么毛病,但这只是因为返回的结果比较短;
当服务端内容比较多时会把结果分成多个包返回来,socket是不管应用层内容的定界的,哪几个tcp包是才一个完整的http响应需要你自己去定界,这是一个很痛苦的事情。http服务如此,其他应用层服务也如此。
python访问http服务,还是推荐使用requests库。
五、关于只有IDE自己的请求才走IDE代理的说明
像PyCharm这种IDE都有一个代理配置,当我和测试同事说想让他们的测试用例走代理时他第一反应是找IDE的这个代配置,我一时也有点懵了这到底能这么实现的吗。
回头测试了一下,这个代理设置只能是说让IDE本身的如检查更新这类流量走这个代理,IDE中你自己的编写的代码的请求他是管不到的。
参考: