使用Java實現(xiàn)HTTP和HTTPS代理服務(wù)詳解
HTTP代理和HTTPS代理介紹
簡單來說就是其他的服務(wù)器代替幫你訪問HTTP或者HTTPS,當(dāng)你的網(wǎng)絡(luò)受限無法訪問某些網(wǎng)站時,但是你的代理服務(wù)器可以訪問某些網(wǎng)站,這時候你就設(shè)置代理,通過代理服務(wù)器去訪問某些網(wǎng)站。例如:你使用某個ip頻繁去爬網(wǎng)站信息時,可能你的ip就會被封,這時候你可能就需要代理去切換ip了。
HTTP代理
首先我們簡單用Java搭一個Socket服務(wù)端,打印一下HTTP代理請求報文。
public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("Server started, waiting for client..."); while (true) { Socket socket = serverSocket.accept(); System.out.println("Client connected: " + socket.getInetAddress().getHostAddress()); new Thread(()->{ try(InputStream inputStream = socket.getInputStream()) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); while (reader.ready()){ System.out.println(reader.readLine()); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } }
使用curl -x 127.0.0.1:8888 http://www.baidu.com
設(shè)置本地代理8888端口,代理訪問百度。
然后看一下Java的控制臺,可以看到代理請求過來的HTTP報文。
GET http://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
User-Agent: curl/8.4.0
Accept: */*
Proxy-Connection: Keep-Alive
下面是通火狐瀏覽器拿到的請求頭原始報文,可以發(fā)現(xiàn)代理HTTP報文和正常HTTP報文不同處:
第一行中間一個是http://www.baidu.com/
,另一個是/
。
一個是Proxy-Connection
,另一個是Connection
。
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: BIDUPSID=52C8D6333DE11AAB7F6B193EE8925F3E; PSTM=1700558835; H_PS_PSSID=40300_40446_40080_60138_40463_60175; BAIDUID=52C8D6333DE11AAB7F6B193EE8925F3E:FG=1; BD_UPN=13314752; COOKIE_SESSION=8189645_0_5_4_2_3_1_0_4_3_31_1_41_0_56_0_1713792557_0_1713792501%7C5%230_0_1713792501%7C1; baikeVisitId=3cf47ba3-c45a-4e5c-bc50-1cef890ac1f1; BA_HECTOR=a02g018h000l2l250k2k8k043iu9l41j2u8gd1t; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598
Upgrade-Insecure-Requests: 1
根據(jù)上面的不同,我們只需要將代理的報文轉(zhuǎn)成正常的HTTP報文,在Java中添加下面兩個方法替換一下內(nèi)容。
//替換域名部分,將http://www.baidu.com 變成 / private static String replaceDomain(String line) { String reqUrl = line.split("\s+")[1]; //http://www.baidu.com/ // 提取域名部分 int endIndex = reqUrl.indexOf("/", 8); // 從第 8 個字符開始搜索斜杠 String domain = endIndex != -1 ? reqUrl.substring(0, endIndex + 1) : reqUrl; // 替換 return line.replaceFirst(domain, "/"); } //Connection部分,將Proxy-Connection 變成 Connection private static String replaceConnection(String line) { String prefix = "Proxy-Connection: "; return line.replaceFirst(prefix, "Connection: "); }
報文修改后,還得知道這個報文往那個地方發(fā)送,可以通Host字段提出域名和端口,代碼如下可以拿到Host。
private static Object[] getHeader(String str) { String hostPrefix = "Host: "; String contentLengthPrefix = "Content-Length: "; String host = null; Integer contentLength = null; String[] line = str.split("\r\n"); for (String l : line) { if (l.startsWith(hostPrefix)) { host = l.substring(hostPrefix.length()); } else if (l.startsWith(contentLengthPrefix)) { contentLength = Integer.parseInt(l.substring(contentLengthPrefix.length())); } } return new Object[]{host, contentLength}; }
有了Host我可以從里面拿到域名和端口,然后進行Socket連接,然后把修改好的報文寫入Socket中。
//獲取host String address = (String) getHeader(sb.toString())[0]; //分割host和端口 String[] split = address.split(":"); String host = split[0]; int port = split.length > 1 ? Integer.parseInt(split[1]) : 80; System.out.println(sb.toString()); //初始化socket連接 socket = new Socket(host, port); //寫入第一次請求的數(shù)據(jù) socket.getOutputStream().write(sb.toString().getBytes());
之后創(chuàng)建一個線程去讀Socket返回的數(shù)據(jù),也就是百度那邊返回的數(shù)據(jù),拿到數(shù)據(jù)后再寫回給代理的clientSocket
。
//創(chuàng)建新線程執(zhí)行socket讀取 Socket finalSocket = socket; new Thread(() -> { try { InputStream socketInputStream = finalSocket.getInputStream(); byte[] socketBt = new byte[1024]; int socketlen = -1; while ((socketlen = socketInputStream.read(socketBt)) != -1) { System.out.println(new String(socketBt, 0, socketlen)); clientSocket.getOutputStream().write(socketBt, 0, socketlen); } socketInputStream.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { finalSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }).start();
這樣就完成了一半,比較麻煩的是在每次連接讀取的第一個代理報文,你得判斷結(jié)束,GET
請求可以通過\r\n\r\n
,POST
就得通過Content-Length
去判斷了,總數(shù)據(jù)長度 = Content-Length
+ 數(shù)據(jù)中的\r\n\r\n
位置后。下面代碼可能長些,就是判斷每次連接的第一個代理報文結(jié)束,結(jié)束標(biāo)志要么用Content-Length
或者\r\n\r\n
,拿到第一個代理報文就可以創(chuàng)建Socket連接,之后的數(shù)據(jù)寫入這個Socket就行了。
InputStream inputStream = clientSocket.getInputStream(); StringBuilder sb = new StringBuilder(); byte[] bt = new byte[1024]; int len = -1; boolean isFirstLine = true; int contentLength = -1; while ((len = inputStream.read(bt)) != -1) { //第一次請求 if (socket == null) { if (isFirstLine) { isFirstLine = false; String replace = replaceDomain(bt, len); //跳過HTTPS連接 if (replace.startsWith("CONNECT")) { len = -1; break; } sb.append(replace); } else { sb.append(new String(bt, 0, len)); } if (contentLength == -1) { Integer length = (Integer) getHeader(new String(bt, 0, len))[1]; if (length != null) { contentLength = length; } int crlfcrlf = findCRLFCRLF(bt, len) + 4; contentLength -= (len - crlfcrlf); } //長度到達結(jié)束 if (contentLength == 0) { break; } if (sb.toString().endsWith("\r\n\r\n")) { break; } } else { //首行需要修改 if (isFirstLine) { String replace = replaceDomain(bt, len); socket.getOutputStream().write(replace.getBytes()); } else { socket.getOutputStream().write(bt, 0, len); } } }
使用curl -x 127.0.0.1:8888 http://www.baidu.com
測試一下,發(fā)現(xiàn)正常響應(yīng)。
上面是簡單的測試,還得用瀏覽器去測試,話說找一個http的網(wǎng)站,也太難,我這里就用python的文件下載管理頁面。
if __name__ == '__main__': # 定義服務(wù)器的端口 PORT = 8000 # 創(chuàng)建請求處理程序 Handler = http.server.SimpleHTTPRequestHandler # 設(shè)置工作目錄 os.chdir("C:\Windows\Media") # 創(chuàng)建服務(wù)器 with socketserver.TCPServer(("", PORT), Handler) as httpd: print(f"服務(wù)啟動在端口 {PORT}") httpd.serve_forever()
使用火狐瀏覽設(shè)置代理。
搞定,瀏覽器和控制臺顯示正常,注意瀏覽器的地址不要寫127.0.0.1,他是不走代理的。
HTTPS代理
HTTPS代理比HTTP代理簡單多了,也是通過Socket服務(wù)端,使用curl -x 127.0.0.1:8888 https://www.baidu.com
控制臺可以拿到下面HTTPS的代理報文。
CONNECT www.baidu.com:443 HTTP/1.1
Host: www.baidu.com:443
User-Agent: curl/8.4.0
Proxy-Connection: Keep-Alive
我簡單畫一下大致流程,瀏覽器通過代理把上面CONNECT報文發(fā)送Java應(yīng)用,Java應(yīng)用拿到報文,從報文中獲取到域名和端口,通過域名和端口連接百度服務(wù)器,連接成功后,回應(yīng)瀏覽器HTTP/1.1 200 Connection estabished
告訴他這邊已經(jīng)連接好了,可以發(fā)送數(shù)據(jù)了,最后瀏覽器和百度服務(wù)器雙方交換數(shù)據(jù)。
瀏覽器Java應(yīng)用百度服務(wù)器CONNECT報文連接HTTP/1.1 200 Connection estabished發(fā)送數(shù)據(jù)寫入數(shù)據(jù)瀏覽器Java應(yīng)用百度服務(wù)器
邏輯和HTTP一樣的,只是他不用把CONNECT報文發(fā)送到百度服務(wù)器上,就不用修改報文,而且結(jié)束只用通過\r\n\r\n
判斷,和百度服務(wù)器連接上就回應(yīng)HTTP/1.1 200 Connection estabished
,簡單多了。
以上就是使用Java實現(xiàn)HTTP和HTTPS代理服務(wù)詳解的詳細內(nèi)容,更多關(guān)于Java實現(xiàn)HTTP和HTTPS代理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談SpringMVC之視圖解析器(ViewResolver)
本篇文章主要介紹了淺談SpringMVC之視圖解析器(ViewResolver),具有一定的參考價值,有興趣的可以了解一下2017-08-08IDEA配置maven環(huán)境的詳細教程(Unable to import maven project報錯問題的解決)
這篇文章主要介紹了IDEA配置maven環(huán)境的詳細教程(Unable to import maven project問題的解決),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06SpringBoot整合Drools規(guī)則引擎動態(tài)生成業(yè)務(wù)規(guī)則的實現(xiàn)
本文主要介紹了SpringBoot整合Drools規(guī)則引擎動態(tài)生成業(yè)務(wù)規(guī)則的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12Java編程實現(xiàn)beta分布的采樣或抽樣實例代碼
這篇文章主要介紹了Java編程實現(xiàn)beta分布的采樣或抽樣實例,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-01-01如何解決redis的NOAUTH Authentication required異常
這篇文章主要介紹了Jedis異常解決:NOAUTH Authentication required,,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值2019-07-07