在網路請求過程中,使用代理是一種常見的需求。代理伺服器可以幫助我們隱藏真實的 IP 地址、加速存取速度、存取公司特定內網等等要求。在 Java 中,我們可以通過一些庫或框架來實現代理的設定和使用。
但如果使用 OkHttp、HttpClient 亦或是 Retrofit 和 Feign,需要實現 Socks 協定代理都需要實現SSLSocketFactory
類或ConnectionSocketFactory
介面的子類,重寫createSokcet
方法,實現起來非常的麻煩。如果代理還需要使用者名稱密碼驗證(大部分都會有),還需要實現Authenticator
的子類,並通過ThreadLocal
分配到請求各自的執行緒中,整個過程需要自己寫很多程式碼,無比煩人。
而本文將介紹如何使用一種最簡單的方法,即使用宣告式 HTTP 框架 Forest,結合@HTTPProxy
和 @SocksProxy
註解來傳送 HTTP/HTTPS 請求,來快速實現代理功能。
新增 Forest 依賴
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.33</version>
</dependency>
如果您的專案不是 spring-boot 專案,請看官方檔案來設定不同環境下的依賴。
先看看沒有代理的情況
// 定義一個 Forest 使用者端介面
public interface MyClient {
// 當呼叫該方法時,會自動使用 Get 請求存取地址 https://example.com
@Get("https://example.com")
String getData();
}
假如https://example.com
這個地址是需要通過代理才能正常存取,那麼以下程式碼將不會成功
// 注入 Forest 使用者端範例
@Resource
MyClient myClient;
... ...
// 網路請求將會失敗
String data = myClient.getData();
在介面上掛上@HTTPProxy
介面即可
// 通過 @HTTPProxy 註解設定代理服務的地址和埠
@HTTPProxy(host = "127.0.0.1", port = "1081")
public interface MyClient {
@Get("https://example.com")
String getData();
}
如果代理服務需要驗證
// 通過 @HTTPProxy 註解設定代理服務的地址和埠以及使用者驗證資訊
@HTTPProxy(host = "127.0.0.1", port = "1081", username = "root", password = "123456")
public interface MyClient {
@Get("https://example.com")
String getData();
}
如果您需要連的是 Socks 協定的代理埠,那也很簡單,可以用上面的方法如法炮製,只不過註解名換了一下而已
// 通過 @SocksProxy 註解設定 Socks 協定代理服務的地址和埠
@SocksProxy(host = "127.0.0.1", port = "1081")
public interface MyClient {
@Get("https://example.com")
String getData();
}
加上使用者名稱密碼
// 通過 @SocksProxy 註解設定 Socks 協定代理服務的地址和埠以及使用者驗證資訊
@SocksProxy(host = "127.0.0.1", port = "1081", username = "root", password = "123456")
public interface MyClient {
@Get("https://example.com")
String getData();
}
如果不想把代理的引數(host, port 等)寫死在註解程式碼中,可以通過字串模板來參照組態檔的屬性
先在application.yml
組態檔中新增以下設定(屬性名可以自己隨意起):
proxy:
host: 127.0.0.1
port: 1081
username: root
password: 123456
通過字串模板在註解中進行參照
@SocksProxy(
host = "#{proxy.host}",
port = "#{proxy.port}",
username = "#{proxy.username}",
password = "#{proxy.password}"
)
public interface MyClient {
@Get("https://example.com")
String getData();
}
如果您有很多介面類要設定代理,並且不想在每個介面上放這麼一大坨引數,可以使用自定義註解對@HTTPProxy
或@SocksProxy
進行封裝
// 自定義一個註解 @MyProxy
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
// 將 @SockProxy 註解以及引數新增到這裡
@SocksProxy(
host = "#{proxy.host}",
port = "#{proxy.port}",
username = "#{proxy.username}",
password = "#{proxy.password}"
)
public @interface MyProxy {
}
然後在需要代理的介面上掛上您自定義的@MyProxy
註解就可以了
@MyProxy
public interface MyClient1 {
@Get("https://example.com/data1")
String getData1();
}
@MyProxy
public interface MyClient2 {
@Get("https://example.com/data2")
String getData2();
}
此時,MyClient1 和 MyClient2 介面的請求都會走同樣的代理
以上都是以宣告式的方式,配合@HTTProxy
以及@SocksProxy
註解來完成 HTTP/Socks 代理的設定過程的。
如果不想定義介面、設定、註解等等玩意兒,那用程式設計式的API直接幹就完了。
// 通過 HTTP 的代理傳送請求
String data1 = Forest.get("https://example.com")
.proxy(ForestProxy.http("127.0.0.1", 1081)
.username("root")
.password("123456"))
.executeAsString();
// 通過 Socks 的代理傳送請求
String data2 = Forest.get("https://example.com")
.proxy(ForestProxy.socks("127.0.0.1", 1081)
.username("root")
.password("123456"))
.executeAsString();