本文主要实现隧道代理,让 https 请求也能代理。
隧道代理的原理是:
HTTP 客户端通过 CONNECT 方法请求隧道代理创建一条到达任意目的服务器和端口的 TCP 连接,并对客户端和服务器之间的后继数据进行盲转发。
步骤如下
1. 客户端发送一个 http CONNECT 请求
CONNECT baidu.com:443 HTTP/1.1
2. 代理收到这样的请求后,拿到目标服务器域名及端口,与目标服务端建立 TCP 连接,并响应给浏览器这样一个 HTTP 报文:
HTTP/1.1 200 Connection Established
3. 建立完隧道以后,客户端与目标服务器照之前的方式发送请求,代理节点只是做转发功能,无从知道转发的流量具体是什么
看代码
import socket
import select
from http.server import BaseHTTPRequestHandler, HTTPServer
class ProxyHandler(BaseHTTPRequestHandler):
def send_data(self, sock, data):
print(data)
bytes_sent = 0
while True:
r = sock.send(data[bytes_sent:])
if r < 0:
return r
bytes_sent += r
if bytes_sent == len(data):
return bytes_sent
def handle_tcp(self, sock, remote):
# 处理 client socket 和 remote socket 的数据流
try:
fdset = [sock, remote]
while True:
# 用 IO 多路复用 select 监听套接字是否有数据流
r, w, e = select.select(fdset, [], [])
if sock in r:
data = sock.recv(4096)
if len(data) <= 0:
break
result = self.send_data(remote, data)
if result < len(data):
raise Exception('failed to send all data')
if remote in r:
data = remote.recv(4096)
if len(data) <= 0:
break
result = self.send_data(sock, data)
if result < len(data):
raise Exception('failed to send all data')
except Exception as e:
raise(e)
finally:
sock.close()
remote.close()
def do_CONNECT(self):
# 解析出 host 和 port
uri = self.path.split(":")
host, port = uri[0], int(uri[1])
host_ip = socket.gethostbyname(host)
remote_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_sock.connect((host_ip, port))
# 告诉客户端 CONNECT 成功
self.wfile.write("{protocol_version} 200 Connection Established\r\n\r\n".format(protocol_version=self.protocol_version).encode())
# 转发请求
self.handle_tcp(self.connection, remote_sock)
def main():
try:
server = HTTPServer(('', 8888), ProxyHandler)
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
if __name__ == '__main__':
main()
有一个 do_CONNECT 函数的处理,实现之前隧道的建立,然后 handle_tcp,代码和之前 socks5 代理是一样的。