Python——實現代理服務功能


代理服務原理很簡單,就拿瀏覽器與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()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM