Python实现一个简单的HTTP代理
生活随笔
收集整理的這篇文章主要介紹了
Python实现一个简单的HTTP代理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 前言
- 原理
- 實現
- 解析HTTP請求數據包
- 代理部分
前言
使用Python寫了一個簡單的HTTP代理(技術棧:socket、http、select),為了方便回顧,記錄在此
原理
角色:客戶端、代理端、服務端
(1) 接收并解析客戶端的請求數據包得到服務端的host信息(主機名和端口)、客戶端的請求方法method和所需資源標識符uri,修正數據包請求行中的uri部分
(2) 兩種情況(根據請求行中method判斷)
實現
完整代碼地址: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代理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 照片估计明星身高matlab,娱乐圈部分
- 下一篇: 在你的 Android 手机上「云养猫」