Skip to content

Commit df73c29

Browse files
authoredAug 17, 2021
first commit
0 parents  commit df73c29

File tree

3 files changed

+295
-0
lines changed

3 files changed

+295
-0
lines changed
 

‎LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 WengChaoxi
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 一个简单的HTTP代理
2+
3+
## 参数说明
4+
5+
```sh
6+
-h, --host 指定代理主机地址,默认0.0.0.0,代表本机任意ipv4地址
7+
-p, --port 指定代理主机端口,默认8080
8+
-l, --listen 指定监听客户端数量,默认10
9+
-b, --bufsize 指定数据传输缓冲区大小,值为整型,单位kb,默认8
10+
-d, --delay 指定数据转发延迟,值为浮点型,单位ms,默认1
11+
```
12+
13+
## 简单使用
14+
15+
### 服务端
16+
17+
```sh
18+
# 启动服务
19+
[wcx@localhost ~]$ python simple_http_proxy.py --bufsize 64
20+
[info] bind=0.0.0.0:8080
21+
[info] listen=10
22+
[info] bufsize=64kb, delay=1ms
23+
```
24+
25+
> 注:Linux查看本机IP地址的命令为 ifconfig,Windows为ipconfig
26+
27+
### 客户端
28+
29+
电脑:打开网络和Internet设置 -> 代理 -> 手动设置代理 -> 配置代理服务器IP和端口号
30+
31+
手机:选择一个已连接的WIFI,修改该网络 -> 显示高级选项 -> 手动设置代理 -> 配置代理服务器IP和端口号
32+

‎simple_http_proxy.py

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
simple-http-proxy ( https://github.com/WengChaoxi/simple-http-proxy )
5+
~ ~ ~ ~ ~ ~
6+
一个简单的http代理
7+
8+
:copyright: (c) 2021 by WengChaoxi.
9+
:license: MIT, see LICENSE for more details.
10+
"""
11+
from __future__ import print_function
12+
13+
import socket
14+
import select
15+
import time
16+
17+
def debug(tag, msg):
18+
print('[%s] %s' % (tag, msg))
19+
20+
class HttpRequestPacket(object):
21+
'''
22+
HTTP请求包
23+
'''
24+
def __init__(self, data):
25+
self.__parse(data)
26+
27+
def __parse(self, data):
28+
'''
29+
解析一个HTTP请求数据包
30+
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
31+
32+
参数:data 原始数据
33+
'''
34+
i0 = data.find(b'\r\n') # 请求行与请求头的分隔位置
35+
i1 = data.find(b'\r\n\r\n') # 请求头与请求数据的分隔位置
36+
37+
# 请求行 Request-Line
38+
self.req_line = data[:i0]
39+
self.method, self.req_uri, self.version = self.req_line.split() # 请求行由method、request uri、version组成
40+
41+
# 请求头域 Request Header Fields
42+
self.req_header = data[i0+2:i1]
43+
self.headers = {}
44+
for header in self.req_header.split(b'\r\n'):
45+
k, v = header.split(b': ')
46+
self.headers[k] = v
47+
self.host = self.headers.get(b'Host')
48+
49+
# 请求数据
50+
self.req_data = data[i1+4:]
51+
52+
class SimpleHttpProxy(object):
53+
'''
54+
简单的HTTP代理
55+
56+
客户端(client) <=> 代理端(proxy) <=> 服务端(server)
57+
'''
58+
def __init__(self, host='0.0.0.0', port=8080, listen=10, bufsize=8, delay=1):
59+
'''
60+
初始化代理套接字,用于与客户端、服务端通信
61+
62+
参数:host 监听地址,默认0.0.0.0,代表本机任意ipv4地址
63+
参数:port 监听端口,默认8080
64+
参数:listen 监听客户端数量,默认10
65+
参数:bufsize 数据传输缓冲区大小,单位kb,默认8kb
66+
参数:delay 数据转发延迟,单位ms,默认1ms
67+
'''
68+
self.socket_proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
69+
self.socket_proxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 将SO_REUSEADDR标记为True, 当socket关闭后,立刻回收该socket的端口
70+
self.socket_proxy.bind((host, port))
71+
self.socket_proxy.listen(listen)
72+
73+
self.socket_recv_bufsize = bufsize*1024
74+
self.delay = delay/1000.0
75+
76+
debug('info', 'bind=%s:%s' % (host, port))
77+
debug('info', 'listen=%s' % listen)
78+
debug('info', 'bufsize=%skb, delay=%sms' % (bufsize, delay))
79+
80+
def __del__(self):
81+
self.socket_proxy.close()
82+
83+
def __connect(self, host, port):
84+
'''
85+
解析DNS得到套接字地址并与之建立连接
86+
87+
参数:host 主机
88+
参数:port 端口
89+
返回:与目标主机建立连接的套接字
90+
'''
91+
# 解析DNS获取对应协议簇、socket类型、目标地址
92+
# getaddrinfo -> [(family, sockettype, proto, canonname, target_addr),]
93+
(family, sockettype, _, _, target_addr) = socket.getaddrinfo(host, port)[0]
94+
95+
tmp_socket = socket.socket(family, sockettype)
96+
tmp_socket.setblocking(0)
97+
tmp_socket.settimeout(5)
98+
tmp_socket.connect(target_addr)
99+
return tmp_socket
100+
101+
def __proxy(self, socket_client):
102+
'''
103+
代理核心程序
104+
105+
参数:socket_client 代理端与客户端之间建立的套接字
106+
'''
107+
# 接收客户端请求数据
108+
req_data = socket_client.recv(self.socket_recv_bufsize)
109+
if req_data == b'':
110+
return
111+
112+
# 解析http请求数据
113+
http_packet = HttpRequestPacket(req_data)
114+
115+
# 获取服务端host、port
116+
if b':' in http_packet.host:
117+
server_host, server_port = http_packet.host.split(b':')
118+
else:
119+
server_host, server_port = http_packet.host, 80
120+
121+
# 修正http请求数据
122+
tmp = b'%s//%s' % (http_packet.req_uri.split(b'//')[0], http_packet.host)
123+
req_data = req_data.replace(tmp, b'')
124+
125+
# HTTP
126+
if http_packet.method in [b'GET', b'POST', b'PUT', b'DELETE', b'HEAD']:
127+
socket_server = self.__connect(server_host, server_port) # 建立连接
128+
socket_server.send(req_data) # 将客户端请求数据发给服务端
129+
130+
# HTTPS,会先通过CONNECT方法建立TCP连接
131+
elif http_packet.method == b'CONNECT':
132+
socket_server = self.__connect(server_host, server_port) # 建立连接
133+
134+
success_msg = b'%s %d Connection Established\r\nConnection: close\r\n\r\n'\
135+
%(http_packet.version, 200)
136+
socket_client.send(success_msg) # 完成连接,通知客户端
137+
138+
# 客户端得知连接建立,会将真实请求数据发送给代理服务端
139+
req_data = socket_client.recv(self.socket_recv_bufsize) # 接收客户端真实数据
140+
socket_server.send(req_data) # 将客户端真实请求数据发给服务端
141+
142+
# 使用select异步处理,不阻塞
143+
self.__nonblocking(socket_client, socket_server)
144+
145+
def __nonblocking(self, socket_client, socket_server):
146+
'''
147+
使用select实现异步处理数据
148+
149+
参数:socket_client 代理端与客户端之间建立的套接字
150+
参数:socket_server 代理端与服务端之间建立的套接字
151+
'''
152+
_rlist = [socket_client, socket_server]
153+
is_recv = True
154+
while is_recv:
155+
try:
156+
# rlist, wlist, elist = select.select(_rlist, _wlist, _elist, [timeout])
157+
# 参数1:当列表_rlist中的文件描述符fd状态为readable时,fd将被添加到rlist中
158+
# 参数2:当列表_wlist中存在文件描述符fd时,fd将被添加到wlist
159+
# 参数3:当列表_xlist中的文件描述符fd发生错误时,fd将被添加到elist
160+
# 参数4:超时时间timeout
161+
# 1) 当timeout==None时,select将一直阻塞,直到监听的文件描述符fd发生变化时返回
162+
# 2) 当timeout==0时,select不会阻塞,无论文件描述符fd是否有变化,都立刻返回
163+
# 3) 当timeout>0时,若文件描述符fd无变化,select将被阻塞timeout秒再返回
164+
rlist, _, elist = select.select(_rlist, [], [], 2)
165+
if elist:
166+
break
167+
for tmp_socket in rlist:
168+
is_recv = True
169+
# 接收数据
170+
data = tmp_socket.recv(self.socket_recv_bufsize)
171+
if data == b'':
172+
is_recv = False
173+
continue
174+
175+
# socket_client状态为readable, 当前接收的数据来自客户端
176+
if tmp_socket is socket_client:
177+
socket_server.send(data) # 将客户端请求数据发往服务端
178+
# debug('proxy', 'client -> server')
179+
180+
# socket_server状态为readable, 当前接收的数据来自服务端
181+
elif tmp_socket is socket_server:
182+
socket_client.send(data) # 将服务端响应数据发往客户端
183+
# debug('proxy', 'client <- server')
184+
185+
time.sleep(self.delay) # 适当延迟以降低CPU占用
186+
except Exception as e:
187+
break
188+
189+
socket_client.close()
190+
socket_server.close()
191+
192+
def client_socket_accept(self):
193+
'''
194+
获取已经与代理端建立连接的客户端套接字,如无则阻塞,直到可以获取一个建立连接套接字
195+
196+
返回:socket_client 代理端与客户端之间建立的套接字
197+
'''
198+
socket_client, _ = self.socket_proxy.accept()
199+
return socket_client
200+
201+
def handle_client_request(self, socket_client):
202+
try:
203+
self.__proxy(socket_client)
204+
except:
205+
pass
206+
207+
def start(self):
208+
try:
209+
import _thread as thread # py3
210+
except ImportError:
211+
import thread # py2
212+
while True:
213+
try:
214+
# self.handle_client_request(self.client_socket_accept())
215+
thread.start_new_thread(self.handle_client_request, (self.client_socket_accept(), ))
216+
except KeyboardInterrupt:
217+
break
218+
219+
if __name__ == '__main__':
220+
# 默认参数
221+
host, port, listen, bufsize, delay = '0.0.0.0', 8080, 10, 8, 1
222+
223+
import sys, getopt
224+
try:
225+
opts, _ = getopt.getopt(sys.argv[1:], 'h:p:l:b:d:', ['host=', 'port=', 'listen=', 'bufsize=', 'delay='])
226+
for opt, arg in opts:
227+
if opt in ('-h', '--host'):
228+
host = arg
229+
elif opt in ('-p', '--port'):
230+
port = int(arg)
231+
elif opt in ('-l', '--listen'):
232+
listen = int(arg)
233+
elif opt in ('-b', '--bufsize'):
234+
bufsize = int(arg)
235+
elif opt in ('-d', '--delay'):
236+
delay = float(arg)
237+
except:
238+
debug('error', 'read the readme.md first!')
239+
sys.exit()
240+
241+
# 启动代理
242+
SimpleHttpProxy(host, port, listen, bufsize, delay).start()

0 commit comments

Comments
 (0)
Please sign in to comment.