標籤: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代理