Java 設定 HTTP/Socks 代理竟能如此簡單

2023-08-30 18:00:45

在網路請求過程中,使用代理是一種常見的需求。代理伺服器可以幫助我們隱藏真實的 IP 地址、加速存取速度、存取公司特定內網等等要求。在 Java 中,我們可以通過一些庫或框架來實現代理的設定和使用。

但如果使用 OkHttp、HttpClient 亦或是 Retrofit 和 Feign,需要實現 Socks 協定代理都需要實現SSLSocketFactory類或ConnectionSocketFactory介面的子類,重寫createSokcet方法,實現起來非常的麻煩。如果代理還需要使用者名稱密碼驗證(大部分都會有),還需要實現Authenticator的子類,並通過ThreadLocal分配到請求各自的執行緒中,整個過程需要自己寫很多程式碼,無比煩人。

而本文將介紹如何使用一種最簡單的方法,即使用宣告式 HTTP 框架 Forest,結合@HTTPProxy@SocksProxy註解來傳送 HTTP/HTTPS 請求,來快速實現代理功能。

Forest 的基本使用

新增 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();

使用 HTTP 代理

在介面上掛上@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 代理

如果您需要連的是 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();