Java 配置 HTTP/Socks 代理竟能如此简单 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
DtFlys
V2EX    Java

Java 配置 HTTP/Socks 代理竟能如此简单

  •  
  •   DtFlys 2023-08-30 17:01:02 +08:00 3044 次点击
    这是一个创建于 830 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在网络请求过程中,使用代理是一种常见的需求。代理服务器可以帮助我们隐藏真实的 IP 地址、加速访问速度、访问公司特定内网等等要求。在 Java 中,我们可以通过一些库或框架来实现代理的设置和使用。

    但如果使用 OkHttp 、HttpClient 亦或是 Retrofit 和 Feign ,需要实现 Socks 协议代理都需要实现SSLSocketFactory类或ConnectionSocketFactor接口的子类,重写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(); 
    16 条回复    2023-08-31 18:12:59 +08:00
    xiaooloong
        1
    xiaooloong  
       2023-08-30 17:07:21 +08:00   3
    你说得对,但是 word 说全文有 1067 个字
    《非常简单》
    NessajCN
        2
    NessajCN  
       2023-08-30 17:12:44 +08:00   1
    python:
    import requests
    resp = requests.get('http://go.to',
    proxies=dict( http='socks5://user:pass@host:port',
    https='socks5://user:pass@host:port'))

    「如此简单」
    DtFlys
        3
    DtFlys  
    OP
       2023-08-30 17:14:40 +08:00   2
    @xiaooloong 如果你认真看完文章,对比过其它 Java 的解决方案,就知道为什么它如此简单了
    DtFlys
        4
    DtFlys  
    OP
       2023-08-30 17:16:03 +08:00
    @NessajCN Python 确实简单,但 Java 里很难找到简单的方案
    aosan926
        5
    aosan926  
       2023-08-30 17:24:47 +08:00
    之前的项目中有用过这个框架,后来在升级 Spring Boot 3 的时候不支持就切到 Retrofit ,感觉也挺好用的
    mmdsun
        6
    mmdsun  
       2023-08-30 17:31:17 +08:00
    感谢分享,框架看起来不错。

    https://www.baeldung.com/spring-6-http-interface
    Java 用新版本 SOCKS 代理会很简单:
    HttpClient.create().proxy(proxyOptions -> proxyOptions.type(ProxyProvider.Proxy.SOCKS5)
    .host(..)
    .port(..)
    );



    如果用 URLConnection con = url.openConnection();可以用启动参数,这种是 HTTP 代理。
    java -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=3128
    DtFlys
        7
    DtFlys  
    OP
       2023-08-30 17:35:01 +08:00
    @mmdsun 感谢分享,Java11 以后的 http 确实要比以前好很多
    DtFlys
        8
    DtFlys  
    OP
       2023-08-30 17:35:46 +08:00
    @aosan926 Forest 1.5.30 版本开始支持 springboot3 了,但 maven 坐标了
    mmdsun
        9
    mmdsun  
       2023-08-30 17:50:12 +08:00
    @DtFlys 这实是 reactor.netty.http.client 的包,spring webflux 项目会引入这个包,配合 spring 的 WebClien 使用。不过,目前仅支持反应式的项目,使用人还比较少。

    @Bean
    public WebClient webClient(){
    return WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector( httpClient))
    }
    vacuitym
        10
    vacuitym  
       2023-08-30 18:00:11 +08:00
    好像启动的时候可以在 jvm 参数里面加代理配置
    DtFlys
        11
    DtFlys  
    OP
       2023-08-30 18:14:22 +08:00
    @vacuitym 是的,但这种方式有 2 个问题:
    1. jvm 参数只能用于全局代理,如果想要只有几个请求走代理,或不同请求走不同的代理,就办不到了,只能自己写代码通过 new Proxy 来实现
    2. jvm 参数不能配置验证信息,如果代理需要密码验证,只能手写 Authenticator 继承类来实现,而且也会碰到全局/局部验证的问题
    fdwjtz
        12
    fdwjtz  
       2023-08-31 04:57:11 +08:00 via Android
    直接改 http_proxy 和 socks_proxy 这俩环境变量行不通么?....
    tedzhou1221
        13
    tedzhou1221  
       2023-08-31 09:15:38 +08:00
    在一个小项目中也使用了 Forest ,的确不错。
    vacuitym
        14
    vacuitym  
       2023-08-31 10:12:28 +08:00
    @DtFlys jvm 加代理是支持密码验证的。其他的确实是问题
    xsen
        15
    xsen  
       2023-08-31 16:05:53 +08:00
    真的,现在一看 java 的各种注解就浑身不舒服
    这真的不叫简单。。lz 恐怕对简单的理解有歧义
    DtFlys
        16
    DtFlys  
    OP
       2023-08-31 18:12:59 +08:00
    @xsen 恐怕你没看到最后,也有非注解,纯编程式的方式
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     782 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 21:33 PVG 05:33 LAX 13:33 JFK 16:33
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86