Python杂记--使用asyncio构建HTTP代理服务器
- 引言
- 基础知识
- 代码实现
引言
本文将介绍 HTTP 代理的基本原理,并带领读者构建一个自己的 HTTP 代理服务器。代码中不会涉及到任何第三方库,全部由 asyncio 实现,性能优秀,安全可靠。
基础知识
HTTP 代理(HyperText Transfer Protocol Proxy)是一种网络代理服务器,充当位于客户端和目标服务器之间的中间人。流程为,首先接收来自客户端的 HTTP 请求,然后转发这些请求到目标服务器,并将目标服务器的响应返回给客户端,如下所示:
在上述流程中,我们需要知道来自客户端的 HTTP 请求的所有内容,包括请求方法,URL等信息,才能实现转发。但众所周知,目前大多数网站都是通过 HTTPS 访问的,而 HTTPS 是加密的,作为代理服务器,拿不到请求方法等敏感信息,那么还能够用 HTTP 代理转发 HTTPS 流量吗?答案是肯定的。此时需要用到 HTTP 中很少见的一种请求方法:CONNECT。客户端首先发送 CONNECT 请求,告诉代理服务器我想连接 A 网站,代理服务器就可以和 A 网站建立连接,实现流量的双向转发,而这不需要对内容进行解密,所以是安全的。具体流程如下所示:
代码实现
原理其实是很简单的,在 Python 中具体实现如下所示:
import asyncio
from urllib.parse import urlparse
async def pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
while True:
data = await reader.read(4096)
if len(data) == 0:
break
writer.write(data)
async def http_proxy(c_reader: asyncio.StreamReader, c_writer: asyncio.StreamWriter, url: str, header: bytes):
result = urlparse(url)
host, port = result.hostname, 80 if result.port is None else result.port
s_reader, s_writer = await asyncio.open_connection(host, port)
s_writer.write(header)
async with asyncio.TaskGroup() as tg:
tg.create_task(pipe(c_reader, s_writer))
tg.create_task(pipe(s_reader, c_writer)).add_done_callback(lambda _: c_reader.feed_eof())
s_writer.close()
c_writer.close()
async def https_proxy(c_reader: asyncio.StreamReader, c_writer: asyncio.StreamWriter, url: str):
host, port = url.split(':')
s_reader, s_writer = await asyncio.open_connection(host, port)
c_writer.write(b'HTTP/1.1 200 Connection Established\r\n\r\n')
async with asyncio.TaskGroup() as tg:
tg.create_task(pipe(c_reader, s_writer))
tg.create_task(pipe(s_reader, c_writer)).add_done_callback(lambda _: c_reader.feed_eof())
s_writer.close()
c_writer.close()
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
header = await reader.readuntil(b'\r\n\r\n')
method, url, _ = header.decode('utf-8').splitlines()[0].split(' ')
if method == 'CONNECT':
return await https_proxy(reader, writer, url)
await http_proxy(reader, writer, url, header)
async def main():
server = await asyncio.start_server(handler, '127.0.0.1', 8080)
async with server:
await server.serve_forever()
asyncio.run(main())
运行后就在本地构建好了代理,我们使用 requests 库进行测试:
import requests
response = requests.get('https://www.baidu.com', proxies={'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'})