使用Tornado實現http代理

來源:互聯網
上載者:User

標籤:except   出錯   add   starting   socket   預設   tracking   src   應用   

0x00 http代理

http代理的用處非常多,市面上也有公開的代理,可是有時候為了工作須要,比方分析應用程式層流量、做資料訪問控制、甚至做監控等等。Tornado提供了一些非常方便的環境和API,我們能夠基於Tornado輕鬆實現一個http代理。

0x01 實現原理

http代理主要做client和web伺服器之間的轉寄。這是大家都熟悉的情境,但僅僅限於http協議的情形。對於https的情況。這時候代理僅僅作為TCP中繼進行資訊中轉,須要單獨處理。


0x02 Tornado實現

基於Tornado能夠實現一個非同步http代理,效能優越,實現也簡單,主要使用的類是AsyncHTTPClient,IOStream。

閱讀過Tornado原始碼的同學可能對這兩個類並不陌生。

這裡還是簡單說下,AsyncHTTPClient顧名思義,是用來做非同步HTTPclient請求的。而IOStream是對socket的一層封裝。


AsyncHTTPClient就是用來處理普通的http請求的。RequestHandler擷取client請求之後,proxy須要解析client的請求並使用這個類來請求伺服器,拿到response,然後寫給client。打完收工。
對於proxy作為TCP中繼的時候,事實上全然能夠使用原生的socket兩頭兒讀寫資料,只是太麻煩了。Tornado提供了一個IOStream類,這個類能夠看做是socket的封裝類,用起來比socket簡單很多。而且socket是非同步非堵塞的。
Talk is cheap, show me the code,不多說,看代碼好了,這裡因為一些原因,我僅僅能貼出關鍵區段的代碼,希望閱讀此文的同學能夠自己寫一個出來用,事實上也不難。

處理http請求
    @tornado.web.asynchronous    def get(self):        # 擷取請求體        body = self.request.body        if not body:            body = None        try:            # 代理髮送請求            render_request(                    self.request.uri,                     callback=self.on_response,                    method=self.request.method,                    body=body,                     headers=self.request.headers,                    follow_redirects=False,                    allow_nonstandard_methods=True)        except tornado.httpclient.HTTPError as httperror:            if hasattr(httperror, ‘response‘) and httperror.response:                self.on_response(httperror.response)            else:                self.set_status(500)                self.write(‘Internal server error:\n‘ + str(httperror))                self.finish()

沒啥好說的。接到client請求。直接去請求伺服器即可了。非同步回呼函數是on_response,這個函數裡就處理proxy和client的互動即可了。self.write(response.body)你懂的。
這裡有個坑。就是寫headers的時候。把response的headers照搬設定一遍是會出錯的,造成訪問失敗。這裡我的處理方法是僅僅寫RequestHandler中self._headers存在的頭即可。

TCP中繼實現

對於443連接埠或者瀏覽器的connect請求。代理僅僅能從TCP層入手。轉寄整個HTTP報文。這裡使用的是http協議中的connect方法,在RequestHandler中實現這種方法即可了。


這裡要注意。Tornado預設是不支援http的connect方法的,所以要改動SUPPORTED_METHODS參數才行:

這裡在RequestHandler中加入一個SUPPORTED_METHODS替換父類的即可:

SUPPORTED_METHODS.append(‘CONNECT‘)

順便說下connect方法,這種方法被調用的時候,代理不用關係http層請求的詳細內容,而是直接從TCP層轉寄這個報文給伺服器。

收到時,也是相同的轉寄給client。

CONNECT www.web-tinker.com:80 HTTP/1.1Host: www.web-tinker.com:80Proxy-Connection: Keep-AliveProxy-Authorization: Basic *Content-Length: 0

詳細實現的代碼例如以下:

    @tornado.web.asynchronous    def connect(self):        ‘‘‘        對於HTTPS串連。代理應當作為TCP中繼        ‘‘‘        def req_close(data):            if conn_stream.closed():                return            else:                conn_stream.write(data)        def write_to_server(data):            conn_stream.write(data)        def proxy_close(data):            if req_stream.closed():                return            else:                req_stream.close(data)        def write_to_client(data):            req_stream.write(data)        def on_connect():            ‘‘‘            建立TCP中繼的回調            ‘‘‘            req_stream.read_until_close(req_close, write_to_server)            conn_stream.read_until_close(proxy_close, write_to_client)            req_stream.write(b‘HTTP/1.0 200 Connection established\r\n\r\n‘)        print ‘Starting Conntect to %s‘ % self.request.uri        # 擷取request的socket        req_stream = self.request.connection.stream        # 找到主機連接埠。一般為443        host, port = (None, 443)        netloc = self.request.uri.split(‘:‘)        if len(netloc) == 2:            host, port = netloc        elif len(netloc) == 1:            host = netloc[0]        # 建立iostream        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)        conn_stream = tornado.iostream.IOStream(s)        conn_stream.connect((host, port), on_connect)

我解釋下這兩句:

req_stream.read_until_close(req_close, write_to_server)conn_stream.read_until_close(proxy_close, write_to_client)

短短兩行代碼,加上4個回呼函數,就完畢了資料的中轉。


首先,req_stream是proxy和client之間的socket,能夠通過HTTPRequest擷取到相應的iostream,proxy和server之間的socket就要自己建立了,這裡是conn_stream。
read_until_close方法是iostream中提供的,作用是一直讀資料,直到socket關閉了。
第一行的作用就是從client和proxy之間的socket中讀資料。讀出來之後,寫入到proxy和server之間的socket中。由proxy轉寄。
第二行的作用就是將伺服器資料寫到clientsocket中了,和上面一樣。沒啥好說的。寫入的功能就在四個回呼函數中。
有人奇怪為啥read_until_close有兩個回呼函數。我的理解是第一個回調在關閉的時候調用,第二個回調在不停讀出資料的時候調用。
寫出來用的效果還行:

使用Tornado實現http代理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.