文章目录

  • 前言
  • 原理
  • 实现
    • 解析HTTP请求数据包
    • 代理部分

前言

使用Python写了一个简单的HTTP代理(技术栈:socket、http、select),为了方便回顾,记录在此

原理

角色:客户端、代理端、服务端

  1. 客户端:向代理端发送请求数据包(HTTP方式会直接建立TCP连接并发请求真实的数据包,HTTPS会通过CONNECT方法先与代理端建立TCP连接,之后再通过该连接发送真实的请求数据包)
  2. 代理端
    (1) 接收并解析客户端的请求数据包得到服务端的host信息(主机名和端口)、客户端的请求方法method和所需资源标识符uri,修正数据包请求行中的uri部分
    (2) 两种情况(根据请求行中method判断)

    1. 当method为HTTP的请求方法时:代理端与host信息对应的服务端主机建立TCP连接,并通过TCP连接向服务端转发客户端的请求数据包,待服务端响应返回,代理端将响应数据转发给客户端,完成一次代理
    2. 当method为CONNECT方法时:代理端先与host信息对应的服务端建立TCP连接,连接成功后,代理端向客户端返回成功信息(HTTP/1.1 200 Connection Established\r\nConnection: close\r\n\r\n),客户端收到此信息后,代理端会收到客户端使用HTTP的请求方法发送的真实的请求数据包,并将该数据包通过先前建立的TCP连接发送给服务端,待服务端响应返回,代理端将响应数据转发给客户端,完成一次代理
  3. 服务端:接收并响应请求数据包(在服务端看来,代理端就是客户端)

实现

完整代码地址:simple_http_proxy.py

解析HTTP请求数据包

class HttpRequestPacket(object):'''HTTP请求包'''def __init__(self, data):self.__parse(data)def __parse(self, data):'''解析一个HTTP请求数据包GET http://test.wengcx.top/index.html HTTP/1.1\r\nHost: test.wengcx.top\r\nProxy-Connection: keep-alive\r\nCache-Control: max-age=0\r\n\r\n参数:data 原始数据'''i0 = data.find(b'\r\n') # 请求行与请求头的分隔位置i1 = data.find(b'\r\n\r\n') # 请求头与请求数据的分隔位置# 请求行 Request-Lineself.req_line = data[:i0]self.method, self.req_uri, self.version = self.req_line.split() # 请求行由method、request uri、version组成# 请求头域 Request Header Fieldsself.req_header = data[i0+2:i1]self.headers = {}for header in self.req_header.split(b'\r\n'):k, v = header.split(b': ')self.headers[k] = vself.host = self.headers.get(b'Host')# 请求数据self.req_data = data[i1+4:]

代理部分

class SimpleHttpProxy(object):'''简单的HTTP代理客户端(client) <=> 代理端(proxy) <=> 服务端(server)'''def __init__(self, host='0.0.0.0', port=8080, listen=10, bufsize=8, delay=1):'''初始化代理套接字,用于与客户端、服务端通信参数:host 监听地址,默认0.0.0.0,代表本机任意ipv4地址参数:port 监听端口,默认8080参数:listen 监听客户端数量,默认10参数:bufsize 数据传输缓冲区大小,单位kb,默认8kb参数:delay 数据转发延迟,单位ms,默认1ms'''self.socket_proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.socket_proxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将SO_REUSEADDR标记为True, 当socket关闭后,立刻回收该socket的端口self.socket_proxy.bind((host, port))self.socket_proxy.listen(listen)self.socket_recv_bufsize = bufsize*1024self.delay = delay/1000.0debug('info', 'bind=%s:%s' % (host, port))debug('info', 'listen=%s' % listen)debug('info', 'bufsize=%skb, delay=%sms' % (bufsize, delay))def __del__(self):self.socket_proxy.close()def __connect(self, host, port):'''解析DNS得到套接字地址并与之建立连接参数:host 主机参数:port 端口返回:与目标主机建立连接的套接字'''# 解析DNS获取对应协议簇、socket类型、目标地址# getaddrinfo -> [(family, sockettype, proto, canonname, target_addr),](family, sockettype, _, _, target_addr) = socket.getaddrinfo(host, port)[0]tmp_socket = socket.socket(family, sockettype)tmp_socket.setblocking(0)tmp_socket.settimeout(5)tmp_socket.connect(target_addr)return tmp_socketdef __proxy(self, socket_client):'''代理核心程序参数:socket_client 代理端与客户端之间建立的套接字'''# 接收客户端请求数据req_data = socket_client.recv(self.socket_recv_bufsize)if req_data == b'':return# 解析http请求数据http_packet = HttpRequestPacket(req_data)# 获取服务端host、portif b':' in http_packet.host:server_host, server_port = http_packet.host.split(b':')else:server_host, server_port = http_packet.host, 80# 修正http请求数据tmp = b'%s//%s' % (http_packet.req_uri.split(b'//')[0], http_packet.host)req_data = req_data.replace(tmp, b'')# HTTPif http_packet.method in [b'GET', b'POST', b'PUT', b'DELETE', b'HEAD']:socket_server = self.__connect(server_host, server_port) # 建立连接socket_server.send(req_data) # 将客户端请求数据发给服务端# HTTPS,会先通过CONNECT方法建立TCP连接elif http_packet.method == b'CONNECT':socket_server = self.__connect(server_host, server_port) # 建立连接success_msg = b'%s %d Connection Established\r\nConnection: close\r\n\r\n'\%(http_packet.version, 200)socket_client.send(success_msg) # 完成连接,通知客户端# 客户端得知连接建立,会将真实请求数据发送给代理服务端req_data = socket_client.recv(self.socket_recv_bufsize) # 接收客户端真实数据socket_server.send(req_data) # 将客户端真实请求数据发给服务端# 使用select异步处理,不阻塞self.__nonblocking(socket_client, socket_server)def __nonblocking(self, socket_client, socket_server):'''使用select实现异步处理数据参数:socket_client 代理端与客户端之间建立的套接字参数:socket_server 代理端与服务端之间建立的套接字'''_rlist = [socket_client, socket_server]is_recv = Truewhile is_recv:try:# rlist, wlist, elist = select.select(_rlist, _wlist, _elist, [timeout])# 参数1:当列表_rlist中的文件描述符fd状态为readable时,fd将被添加到rlist中# 参数2:当列表_wlist中存在文件描述符fd时,fd将被添加到wlist# 参数3:当列表_xlist中的文件描述符fd发生错误时,fd将被添加到elist# 参数4:超时时间timeout#  1) 当timeout==None时,select将一直阻塞,直到监听的文件描述符fd发生变化时返回#  2) 当timeout==0时,select不会阻塞,无论文件描述符fd是否有变化,都立刻返回#  3) 当timeout>0时,若文件描述符fd无变化,select将被阻塞timeout秒再返回rlist, _, elist = select.select(_rlist, [], [], 2)if elist:breakfor tmp_socket in rlist:is_recv = True# 接收数据data = tmp_socket.recv(self.socket_recv_bufsize)if data == b'':is_recv = Falsecontinue# socket_client状态为readable, 当前接收的数据来自客户端if tmp_socket is socket_client: socket_server.send(data) # 将客户端请求数据发往服务端# debug('proxy', 'client -> server')# socket_server状态为readable, 当前接收的数据来自服务端elif tmp_socket is socket_server:socket_client.send(data) # 将服务端响应数据发往客户端# debug('proxy', 'client <- server')time.sleep(self.delay) # 适当延迟以降低CPU占用except Exception as e:breaksocket_client.close()socket_server.close()def client_socket_accept(self):'''获取已经与代理端建立连接的客户端套接字,如无则阻塞,直到可以获取一个建立连接套接字返回:socket_client 代理端与客户端之间建立的套接字'''socket_client, _ = self.socket_proxy.accept()return socket_clientdef handle_client_request(self, socket_client):try:self.__proxy(socket_client)except:passdef start(self):try:import _thread as thread # py3except ImportError:import thread # py2while True:try:# self.handle_client_request(self.client_socket_accept())thread.start_new_thread(self.handle_client_request, (self.client_socket_accept(), ))except KeyboardInterrupt:break

项目地址:https://github.com/WengChaoxi/simple-http-proxy

Python实现一个简单的HTTP代理相关推荐

  1. python代理池_用Python搭建一个简单的代理池

    其实每次爬东西的时候,特怕IP被封,所以每次都要把时间延迟设置得长一点...这次用Python搭建一个简单的代理池.获取代理IP,然后验证其有效性.不过结果好像不是很理想,为什么西刺代理的高匿代理都能 ...

  2. python编写登录_通过Python编写一个简单登录功能过程解析

    通过Python编写一个简单登录功能过程解析 需求: 写一个登录的程序, 1.最多登陆失败3次 2.登录成功,提示欢迎xx登录,今天的日期是xxx,程序结束 3.要检验输入是否为空,账号和密码不能为空 ...

  3. python界面设计-手把手教你用Python设计一个简单的命令行界面

    原标题:手把手教你用Python设计一个简单的命令行界面 对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰.今天,我们就来教大家如何设计功能完整的 Pyth ...

  4. python游戏最简单代码-如何利用Python开发一个简单的猜数字游戏

    前言 本文介绍如何使用Python制作一个简单的猜数字游戏. 游戏规则 玩家将猜测一个数字.如果猜测是正确的,玩家赢.如果不正确,程序会提示玩家所猜的数字与实际数字相比是"大(high)&q ...

  5. python推荐系统-利用python构建一个简单的推荐系统

    摘要: 快利用python构建一个属于你自己的推荐系统吧,手把手教学,够简单够酷炫. 本文将利用python构建一个简单的推荐系统,在此之前读者需要对pandas和numpy等数据分析包有所了解. 什 ...

  6. 怎么用python编简单游戏_用Python实现一个简单的算术游戏详解

    用Python实现一个简单的算术游戏 #!/usr/bin/env python from operator import add, sub from random import randint, c ...

  7. 【Python】如何用python做一个简单的输入输出交互界面?

    看到知乎上有人在问,如何使用Python做一个简单的输入输出交互界面? 交互界面就涉及到GUI编程. Python有很多GUI框架,功能大同小异. 其中比较出名的有「PyQT」.**wxPython. ...

  8. 基于python的系统构建_利用python构建一个简单的推荐系统

    摘要: 快利用python构建一个属于你自己的推荐系统吧,手把手教学,够简单够酷炫. 本文将利用python构建一个简单的推荐系统,在此之前读者需要对pandas和numpy等数据分析包有所了解. 什 ...

  9. java调python 监控_利用Python实现一个简单的系统监控图表

    作为运维人员,想必大家肯定都做过这样的事情:为了监控系统资源使用情况,开了若干个窗口,来回切换看输出: 只要我切得够快,性能异常点就逃不过我的眼睛! 这个时候你要是有个监控工具自然是很好的,例如我们美 ...

  10. python自己做个定时器_技术图文:如何利用 Python 做一个简单的定时器类?

    原标题:技术图文:如何利用 Python 做一个简单的定时器类? 背景 今天在B站上看有关 Python 最火的一个教学视频 -- "零基础入门学习 Python",这也是我们 P ...

最新文章

  1. 对微软Web Deploy的一次艰难调试
  2. 解决 Unable to get provider
  3. 五大存储模型关系模型、键值存储、文档存储、列式存储、图形数据
  4. android 开发时遇到的各种问题1--Android双模(CDMA/GSM)手机短信相关
  5. URL URI傻傻分不清楚,dart告诉你该怎么用
  6. 两个或者多个图片上下之间有空隙
  7. 浅谈Web前端安全策略xss和csrf,及又该如何预防?
  8. [十二省联考 2019] 异或粽子(可持久化字典树 + 二叉堆)
  9. MySQL——数据库和表的增删改查
  10. linux下Hbase的常用shell命令
  11. Apache-ab 接口性能测试
  12. C语言中的字符串函数
  13. elementUI压缩图片和将图片转成base64格式
  14. 华工计算机学院专硕分数线,2017华南理工大学
  15. 游戏对战平台研究终结篇
  16. 【C语言】详解 calloc 函数用法
  17. java utc时间_Java获取UTC时间的方法
  18. 火灾检测参考资料与数据集
  19. LYOI 78 小澳的葫芦
  20. 网易租赁服务器怎么添加组件,《我的世界》租赁服添加MOD教程 租赁服怎么添加小精灵MOD?...

热门文章

  1. 入侵WIN2003 PHP服务器的另类技术
  2. Windows系统鼠标右键菜单添加打开cmd终端
  3. MySQL中show profile详解
  4. 新型城镇化3.0时代 数据交换是“智慧城市”的核心
  5. CCNA(思科网络攻城狮) 滴水之力05
  6. 容器 java 时区_docker容器修改时区(java应用log信息与标准容器时间有八个小时时间差)...
  7. 笔记本设置WiFi热点命令操作
  8. MSSQL 负载均衡(Moebius)
  9. 关于AD域和Exchange邮件服务器的学习总结
  10. 龙尚3G模块在arm板上的应用