一、說明
1.1 背景說明
關於“代理”,從burpsuite到ss這類正向代理,再從nginx到haproxy這類反向代理,也用了好多年配置了好多年了。在日積月累之下也確認了以下幾個問題:
正向代理和反向代理的區別----正常訪問路徑是client----public network----server,如果代理服務器處於client和public network之間就是正向代理,如果代理服務器處於public network和server之間就是反向代理。
代理從連接的角度是怎么實現的----A建立一個tcp連接到B發送應用層內容,B另建立一個tcp連接到C轉發A應用層的內容。
socks5是什么和socket庫有什關系和區別----socket我們可以認為是“數據鏈路層+網絡層+傳輸層”的實現庫,socks5是傳輸層之上的一種協議(類比ssl層)
ssl密鑰協商過程是怎樣的----SSL密鑰協商過程分析
怎么編寫代碼實現ssl通信----Python3+ssl實現加密通信
但是仍是有一種不甚了解的感覺,比如這兩天實現socket使用代理訪問web服務代碼就又費了好多功夫。
1.2 環境說明
代理服務----windows本地運行的whistle代理服務,監聽端口8899
實現語言----python3
實現功能----socket通過代理訪問http服務(第二大點)、socket通過代理訪問https服務(第三大點)
二、socket實現通過代理訪問http服務
2.1 實現代碼
import socket # 以http請求www.baidu.com的請求包內容 # http以換行表示請求頭完結,所以最后的空行千萬不要刪掉 http_layer_data = """ GET / HTTP/1.1 Host: www.baidu.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 """ # 第一步,和代理服務器建立連接 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('127.0.0.1', 8899)) # 第二步,向代理服務器發送請求 # 代理服務器識別接收到是http協議內容后,會根據Host頭進行轉發 s.sendall(http_layer_data.encode()) # 第三步,接收到服務器響應並打印 response_data = s.recv(1024) print(response_data.decode()) s.close()
2.2 響應結果
如下圖所示,成功接收到302重定向響應
三、socket實現通過代理訪問https服務
現在大多數服務都不再使用http協議,而是轉而使用https協議。
如果是代碼直接訪問https服務,那問題還是比較明朗的,我們只要先與https服務端建立一個ssl連接,然后再發送http層內容即可。
但是如果中間插入一個代理,這問題就比較棘手:客戶端該用socket和代理服務器連接呢還是該用ssl和代理服務器連接、客戶端又該如何通知代理服務器使用https向https服務端轉發內容。
3.1 代碼實現
import socket import ssl # 以http請求www.baidu.com的請求包內容 # http以換行表示請求頭完結,所以最后的空行千萬不要刪掉 http_layer_data = """ GET / HTTP/1.1 Host: www.baidu.com Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 """ # 第一步,和代理服務器建立socket連接 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", 8899)) # 第二步,代理服務器與https服務端進行密鑰協商並建立ssl連接,用於后續轉發 connect_request_http_layer_data = """ CONNECT www.baidu.com:443 HTTP/1.0 Connection: close """ s.sendall(connect_request_http_layer_data.encode()) response_data = s.recv(1024) print(response_data.decode()) # 第三步,建立ssl連接 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ss = context.wrap_socket(s) # 第四步,向代理服務器發送請求 # 代理服務器會解出其中的http層內容,使用先前和https服務端建立的連接,進行轉發 ss.send(http_layer_data.encode()) # 第五步,接收到服務器響應並打印 response_data = ss.recv(1024) print(response_data.decode()) ss.close()
3.2 響應結果
四、不建議使用socket訪問應用層服務的原因
不建議使用socket訪問應用層服務的原因,其實除了代理這種功能比較費勁外,還有一個更主要的原因。
在上邊的代碼中我們發送一個請求然后接收響應,好像一切都正常沒什么毛病,但這只是因為返回的結果比較短;
當服務端內容比較多時會把結果分成多個包返回來,socket是不管應用層內容的定界的,哪幾個tcp包是才一個完整的http響應需要你自己去定界,這是一個很痛苦的事情。http服務如此,其他應用層服務也如此。
python訪問http服務,還是推薦使用requests庫。
五、關於只有IDE自己的請求才走IDE代理的說明
像PyCharm這種IDE都有一個代理配置,當我和測試同事說想讓他們的測試用例走代理時他第一反應是找IDE的這個代配置,我一時也有點懵了這到底能這么實現的嗎。
回頭測試了一下,這個代理設置只能是說讓IDE本身的如檢查更新這類流量走這個代理,IDE中你自己的編寫的代碼的請求他是管不到的。
參考: