Pythonが実現する簡易HTTPプロキシサーバー

6580 ワード


このソースコードとCtrl+C+Vのソースはこれを参照してください。
socketを使ってプログラミングしてプロキシサーバーを実現して、まずそれはサーバーでなければなりません。ですから、最初の参照コードがあります。
server = socket.socket()
server.bind(('127.0.0.1',8000))
server.listen(3)
conn, addr = server.accept()
data = True
while data :
    data = conn.recv(1024)
    msg = raw_input()
    if msq=="any code you mean to exit": break
    conn.sendall(msg)
conn.close()
server.close()
これらのことをしました。
1.サービスを開始し、ポート8000を傍受し、3つのクライアントが並ばせるように設定する(実際には1つのクライアントのみのアクセスをサポートするが)
2.要求を受け、接続中にデータを受信し、リターンする
3.クライアントが閉じていると、recvは空の文字列を得るため、ループを終了し、プログラムを終了します。
上のこのプログラムで要求を受信してもいいです。私達のプロキシサーバーは一体何を処理しますか?
GEThttp://www.sina.com/ HTTP/1.1 Host:www.sina.com User-Agent:Mozila/5.0(Windows NT 10.0;Win 64;x 64;rv:65.0)Gecko/201001 Firefox/65.0 Accept/html、appication/xhtl+xml、appication/xml;q=0.9、イメージ/webp、*/*;q=0.8 Acceept Language:zh-CSN,zh;q=0.8,zh-TWq=0.7,zh-HKq=0.5,en-USq=0.3,en;q=0.2 Acct-Encecding:gzip,deflate Connection:keep-alive Upgrade-Innsecure-requests:1
 
なお、一番下には2つの空行があり、これは約束であり、要求ヘッドと要求体の間は\r\rで分割される。
よく見えるように、unicodeで表示させてもいいです。
['GEThttp://www.sina.com/ HTTP/1.1\r Host:www.sina.com\rUser-Agent:Mozila/5.0(Windows NT 10.0;Win 64;x 64;rv:65.0)Gecko/201000001 Firefox/65.0\rAcceept:text/html、aplication/xhhml+xml、aplication/xml;q=0.9、image/webp、*/q=0.8\rAcceept Language:zCh- CN、zh;q=0.8,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzrConnection:keep-alive\rUpgrade-Innsecure-requests:1\r\r'
その中の2行目のHostは私達が取得する対象サーバの住所です。もちろんhttpのデフォルトポート番号は80です。
対象サーバの住所とポート番号を入手すれば、この要求をそのまま対象サーバに捨てることができます。どうやってこの宛先を取得すればいいですか?どうせ難しくないです。実現したふりをしてもいいです。
上記のサーバーコードと違って、私達はinputを必要としないし、循環処理データも必要としません。データを受け取り終わったら、サーバーに捨ててもいいです。そしてターゲットサーバからデータを返す過程は正反対です。
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8000))
server.listen(3)
conn, addr = server.accept()
data = conn.recv(1024)
print data
#        getHost
host, port = getHost(data)
target = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
target.connect((host, port))
target.sendall(data)

data = target.recv(1024)
print data
conn.sendall(data)

target.close()
conn.close()
server.close()
wwww.sina.comのテストに対して、このような報告がありました。
HTTP/1.1 302 Moved Temporarly Server:nginx Date:Thu,14 Mar 2019:25:58 GMT Contee-Type:text/html Contee-Length:154 Connection:keep-alive Location:https://www.sina.com.cn/ X-Via-CDN:f=edge、s=cmcccccmccccccmcc.shandong.ha 2 ts 4.82.nb.sinaedge.com、c=223.72.94.28;X-Via-Edge:15552758515 c 5 e 48 df 7 d 53 c 0788 e 9 a 7612
302 Found 302 Found nginx
新聞によると、このサイトはもう引っ越しました。http契約で訪問しなくなりました。今後は新浪ネットに行くべきです。https://www.sina.com.cn/このウェブサイト
明らかに、私は時代遅れなので、httpsで活躍していた時代には伝統のhttp代理さえ覚えていませんでした。
いずれにしても、このような結果は少なくともクライアントとサーバーの応答を正常に受信していることを示しています。そして、ブラウザは新浪網に正常にアクセスできることが分かります。
これでhttpプロキシのコアコードはすでに完成しました。次のタスクはこの部分のコードを最適化することです。
まず、このサーバ起動コマンドではポート番号として整数を受信できます。そして、私達のサーバが複数の異なるクライアントにサービスできるように装っています。これは各クライアントに対してそれぞれ新しいスレッドを起動する必要があることを意味します。
def main(_, port=8000):
    myserver = socket.socket()
    myserver.bind(('127.0.0.1', port))
    myserver.listen(1024)
    while True:
        conn, addr = myserver.accept()
        thread_p = threading.Thread(target=thread_proxy, args=(conn, addr))
        thread_p.setDaemon(True)
        thread_p.start()

if __name__ == '__main__':
    main(*sys.argv)
    sys.exit(0)
もちろんです。私達のサーバーはごろつきです。脱退方法は提供されませんので、これはデッドサイクルです。
それぞれに対してthread_proxy、私たちは三つのことを完成しなければなりません。2.転送要求メッセージ。3.応答メッセージを転送します。
以前の簡易サーバコードに書いたrecvパラメータは1024に固定されています。これは要求長が1024以下の要求に対してのみプロキシを行うことができ、長さを超えても責任がないということですか?これは当然不適当です。ですから、読み終わるまで非常に大きな循環で読まなければなりません。
そこで非常に気まずい問題が発生しました。ある読み取りが終わった後、どうやって私が読み終わったか分かりますか?
つのとても直観的な考えは:もし私が読んだ長さは予定の長さに等しいならば、それは読み終わっていないので、さもなくば読み終わりました。クライアントでもブラウザでも、予め設定した長さがどれぐらいなのか分かりませんので、いつも「整数倍」の確率があります。この確率はあまり低くないです。そうなると、読書の渋滞に巻き込まれます。
要求ヘッダの中の一つのフィールド「content-length」は、要求体の長さを記述するために使用され、このようなフィールドがない場合、約束\r 0\r\rは休止符である。
ネットで調べた結論は奥が深いですが、簡単に言えば上の言葉です。それに加えて、私たちが以前から身につけていた\r\r分割子は、次のような手段になります。
  • 切取要求ヘッド
    def splitHeader(string):
        i, l = 3, len(string)
        while i
  • 要求ヘッダから情報を探す
    def getHeader(header, name):
        name = name.upper()
        base, i, l = 0, 0, len(header)
    
        while i
  • 約束に基づいて、すべての報文を取得する
    def recvBody(conn, base, size):
        if size==-1:
            while base[-5:] != "\r
    0\r
    \r
    " : base += conn.recv(RECV_SIZE) else: while len(base)

  • これらの力を与える手段によって支えられて、今はthread_を書くことができます。proxyです。便利さのために、実際に多くのサーバーに約束されています。新聞のヘッダ情報は長すぎてはいけません。これは私達に保証を与えました。指定された長さの中で必ず完全なヘッダ情報が得られます。この長さをMAXに設定します。HEADER_SIZE、あります:
    def thread_proxy(client, addr):
    
        request = client.recv(MAX_HEADER_SIZE)
        requestHeader = splitHeader(request)
        raw_host = getHeader(requestHeader, "Host")
        host, port = transHost(raw_host)
        # body        ,        
        if len(requestHeader) < len(request)-4:
            content_size = getHeader(requestHeader, "content-length")
            size = len(requestHeader) + 4 + int(content_size) if content_size else -1
            request = recvBody(client, request, size)
    
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.connect((host, port))
        server.sendall(request)
    
        response = server.recv(MAX_HEADER_SIZE)
        responseHeader = splitHeader(response)
        if len(responseHeader) < len(response)-4:
            content_size = getHeader(responseHeader , "content-length")
            size = len(responseHeader) + 4 + int(content_size) if content_size else -1
            response = recvBody(server, response , size)
    
        client.sendall(response)
        server.close()
        client.close()
    TransHostは非常に簡単な方法です。標準値を処理するのに便利なだけです。単独で抽出します。
    def transHost(raw_host):
        for i in range(len(raw_host)): 
            if raw_host[i] == ":" : return raw_host[:i].strip(), int(raw_host[i+1:])
        else : return raw_host.strip(), 80
    len(reponseHeader)+4+int(content-usize)は技術的な不足を補う解決策であり、新聞の長さを制御することを目的としています。
    これで、基本的なhttpプロキシサーバーが実現されました。もちろん、丈夫さを考慮して、debugの便利さとその他の要素から、実用化コードはもっと長くなります。完全なコードはここをクリックしてください。
    しかしhttpsはもっと複雑になると言われています。今までのところ、案内図さえまだ分かりません。SSL契約を教えてくださいませんか?