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中你自己的编写的代码的请求他是管不到的。

参考:

http://www.fridayhaohao.com/articles/24/

原文地址:https://www.cnblogs.com/lsdb/p/14525027.html