代理服務原理很簡單,就拿瀏覽器與web服務器來說。無非是A瀏覽器
發request給B代理,B代理再把request把送給C web服務,然后C的reponse->B->A。
要寫web代理服務就要先了解下http協議,當然並不要多深入,除非要實現強大的功能:修改XX信息、
負載均衡等。http請求由三部分組成:請求行、消息報頭、請求正文;
詳細的網上有,想了解可以看看。下面是一個正常的GET請求頭(Cookie部分本人沒截屏,使用的系統w7):
可以看到首行:GET是請求方法, /是路徑,在后面是協議版本;第二行以后是請求報頭,都是鍵值對形式;
GET方法沒有正文。post有正文,除此之外,請求方法頭部基本一致,每一行結尾都是\r\n。
基本的請求方法,如下:
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源后附加新的數據
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
PUT 請求服務器存儲一個資源,並用Request-URI作為其標識
DELETE 請求服務器刪除Request-URI所標識的資源
TRACE 請求服務器回送收到的請求信息,主要用於測試或診斷
CONNECT 保留將來使用
OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
但是使用代理后,從代理服務上得到的請求如下:
與第一張圖片對比一下,有什么不同......第一行的資源路徑不對。當瀏覽器上設置代理請求時把整個url都作為資源路徑了,所以我們要把域名刪掉,然后代理服務器在把修改后的請求發送給目標
web服務器。就這么簡單,當然CONNECT方法特別,要特別對待,所以先說其他方法。
基本的思路:
1、代理服務器運行監聽,當有客戶端瀏覽器請求到來時通過accept()獲得client句柄(或者叫描述符);
2、利用client描述符接收瀏覽器發來的request,分離出第一行為了修改第一行和獲得method,
要去掉的的部分,除去http://的部分用targetHost表示吧。
3、通過第2步能夠獲得方法method、request和targetHost,這一步可以根據不同的method做不同的處理,
由於GET、POET、PUT、DELETE等除了CONNECT處理基本一致,所以處理首行,比如:
GET http://www.a.com/ HTTP/1.1
替換為
GET / HTTP/1.1
此時targetHost也就是紅色的部分,默認的請求80端口,此時port為80;如果targetHost中有端口(比如www.a.com:8081),
就要分理處端口,此時port為8081。然后根據targetHost和port連接到目標服務器target了,實現代碼如下:
def getTargetInfo(self,host): #處理targetHost獲得網址和端口,作為返回值。 port=0 site=None if ':' in host: tmp=host.split(':') site=tmp[0] port=int(tmp[1]) else: site=host port=80 return site,port def commonMethod(self,request): #處理除CONNECT以外的方法 tmp=self.targetHost.split('/') net=tmp[0]+'//'+tmp[2] request=request.replace(net,'') #替換掉首行不必要的部分 targetAddr=self.getTargetInfo(tmp[2]) #調用上面的函數 try: (fam,_,_,_,addr)=socket.getaddrinfo(targetAddr[0],targetAddr[1])[0] except Exception as e: print e return self.target=socket.socket(fam) self.target.connect(addr) #連接到目標web服務
4、這一步就好辦了,根據第三步處理后的request就可以self.target.send(request)發送給web服務器了。
5、這一步web服務器的reponse反響通過代理服務直接轉發給客戶端就行了,本人用了非阻塞select,可以試試epoll。
基本步驟就是這樣,使用的方法函數可以改進,比如主函數部分使用的多線程或者多進程,怎樣選擇......
但是思路差不多都是這樣啦。想測試的話,chrome安裝SwitchySharp插件,設置一下,代理端口8083;
firefox插件autoproxy。
對於connect的處理還在解決中(如果有博友幫助就更好了),所以現在這個代理程序不支持https協議。
代理服務可以獲得http協議的所有信息,想了解學習http,利用代理服務器是個不錯的方法。
下面附上代碼
#-*- coding: UTF-8 -*- import socket,select import sys import thread from multiprocessing import Process class Proxy: def __init__(self,soc): self.client,_=soc.accept() self.target=None self.request_url=None self.BUFSIZE=4096 self.method=None self.targetHost=None def getClientRequest(self): request=self.client.recv(self.BUFSIZE) if not request: return None cn=request.find('\n') firstLine=request[:cn] print firstLine[:len(firstLine)-9] line=firstLine.split() self.method=line[0] self.targetHost=line[1] return request def commonMethod(self,request): tmp=self.targetHost.split('/') net=tmp[0]+'//'+tmp[2] request=request.replace(net,'') targetAddr=self.getTargetInfo(tmp[2]) try: (fam,_,_,_,addr)=socket.getaddrinfo(targetAddr[0],targetAddr[1])[0] except Exception as e: print e return self.target=socket.socket(fam) self.target.connect(addr) self.target.send(request) self.nonblocking() def connectMethod(self,request): #對於CONNECT處理可以添加在這里 pass def run(self): request=self.getClientRequest() if request: if self.method in ['GET','POST','PUT',"DELETE",'HAVE']: self.commonMethod(request) elif self.method=='CONNECT': self.connectMethod(request) def nonblocking(self): inputs=[self.client,self.target] while True: readable,writeable,errs=select.select(inputs,[],inputs,3) if errs: break for soc in readable: data=soc.recv(self.BUFSIZE) if data: if soc is self.client: self.target.send(data) elif soc is self.target: self.client.send(data) else: break self.client.close() self.target.close() def getTargetInfo(self,host): port=0 site=None if ':' in host: tmp=host.split(':') site=tmp[0] port=int(tmp[1]) else: site=host port=80 return site,port if __name__=='__main__': host = '127.0.0.1' port = 8083 backlog = 5 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind((host,port)) server.listen(5) while True: thread.start_new_thread(Proxy(server).run,()) # p=Process(target=Proxy(server).run, args=()) #多進程 # p.start()