# okhttp_switch_proxy **Repository Path**: a_bad_fox/okhttp_switch_proxy ## Basic Information - **Project Name**: okhttp_switch_proxy - **Description**: OkHttp 自动切换代理 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2020-12-06 - **Last Updated**: 2023-08-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 原理 OkHttp 支持 `Http`、`Socket`代理,可以在初始化`OkHttpClient`时,设置客户端指定的代理。 ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SwitchProxyInterceptor()) .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 1080))) .build(); ``` 当用户需要动态指定代理时,可以在初始化参数中添加`proxySelector` ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SwitchProxyInterceptor()) .proxySelector(new SwitchProxySelector()) .build(); ``` > `ProxySelector` 是`JDK`中定义的`abstract`类,其中用户需要实现两个函数 > > ```java > /** > * 根据 URL 返回代理列表 > */ > public List select(URI uri) ; > /** > * 代理失败时,可以删除代理等操作 > */ > public void connectFailed(URI uri, SocketAddress sa, IOException ioe) > ``` 虽然`ProxySelector`可以根据URL选择代理,但是当需要根据不同的`request`选择代理时,`ProxySelector`就不能应对这种情况,因为`ProxySelector`没办法得知`request`的任何信息,更无法知道应该返回何种代理。 这时**`ThreadLocal`**就可以解决这一问题。`ThreadLocal`是存储当前线程的数据,当需要调用链路特别长时,上游使用`ThreadLocal`存储某些信息,下游可以直接拿到,而不用把参数从上游一直传递到下游。 **根据这一原理我们可以在`OkHttp`发起调用时,设置代理信息,然后`ProxySelector`获取本`Request`需要返回的代理** ## 实现细节 实现只需要两个类`SwitchProxySelector`和`SwitchProxyInterceptor`。 * `SwitchProxyInterceptor`提取`request`设置的代理 * `SwitchProxySelector`返回需要的的代理 ### `SwitchProxyInterceptor` `SwitchProxyInterceptor`需要检测`request`的`header`有没有`meta.proxy`,然后根据指定的规则往`ThreadLocal`中存入`proxy`,**随后清除`meta.proxy`的`header`** ```java public class SwitchProxyInterceptor implements Interceptor { private final static Logger logger = LoggerFactory.getLogger(SwitchProxyInterceptor.class); @NotNull public Response intercept(@NotNull Chain chain) throws IOException { if(chain.request().header("meta.proxy")!=null){ String proxyHeader = chain.request().header("meta.proxy"); logger.debug("detect proxy header : {}", proxyHeader); SwitchProxySelector.proxyThreadLocal.set(SwitchProxySelector.getProxy(proxyHeader)); Request newRequest = chain.request().newBuilder().removeHeader("meta.proxy").build(); return chain.proceed(newRequest); } return chain.proceed(chain.request()); } } ``` ### `SwitchProxySelector` `SwitchProxySelector`提供工厂方法用来解析`meta.proxy`中的`header`,返回代理 `meta.proxy`的模式为`{socket|http}:{host}:{port}` ```java public class SwitchProxySelector extends ProxySelector { private final static Logger logger = LoggerFactory.getLogger(SwitchProxySelector.class); /** * 根据request返回 */ public List select(URI uri) { Proxy proxy = SwitchProxySelector.proxyThreadLocal.get(); if(proxy==null){ proxy = Proxy.NO_PROXY; } logger.debug("{} use proxy {}:{}", uri.toString(), proxy.type().name(), proxy.address()); SwitchProxySelector.proxyThreadLocal.remove(); return Collections.singletonList(proxy); } public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { } public static ThreadLocal proxyThreadLocal = new ThreadLocal<>(); /** * proxy 模式 */ private static final Pattern PROXY_PATTERN = Pattern.compile("(socket|http):(.*):(.*)"); /** * 工厂方法 获取Proxy * @param proxyString meta.proxy 中的 proxy 字符串 * @return Proxy */ static Proxy getProxy(String proxyString){ if(proxyString==null || "".equals(proxyString)){ return Proxy.NO_PROXY; } Matcher matcher = PROXY_PATTERN.matcher(proxyString); if(matcher.matches()){ switch (matcher.group(1)){ case "socket": return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(matcher.group(2), Integer.parseInt(matcher.group(3)))); case "http": return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(matcher.group(2), Integer.parseInt(matcher.group(3)))); default: return Proxy.NO_PROXY; } } return Proxy.NO_PROXY; } } ``` ## 测试 ```java public class TestSwitchProxy { public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new SwitchProxyInterceptor()) .proxySelector(new SwitchProxySelector()) .build(); Request request1 = new Request.Builder().url("https://www.google.com") .header("meta.proxy", "socket:192.168.0.63:1080") .build(); Request request2 = new Request.Builder().url("http://www.baidu.com").build(); Request request3 = new Request.Builder().url("https://www.google.com") .header("meta.proxy", "http:192.168.0.63:8118") .build(); Callback callback = new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { System.out.println(call.request().url().toString()+" 连接失败"); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { System.out.println(call.request().url().toString() +" 连接成功-"+ response.request().url().toString()); } }; client.newCall(request1).enqueue(callback); client.newCall(request2).enqueue(callback); client.newCall(request3).enqueue(callback); client.newCall(request3).enqueue(callback); client.newCall(request2).enqueue(callback); client.newCall(request1).enqueue(callback); client.newCall(request2).enqueue(callback); client.newCall(request3).enqueue(callback); } } ``` ```java [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxyInterceptor] detect proxy header : http:192.168.0.63:8118 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxyInterceptor] detect proxy header : socket:192.168.0.63:1080 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxyInterceptor] detect proxy header : http:192.168.0.63:8118 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxyInterceptor] detect proxy header : socket:192.168.0.63:1080 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxyInterceptor] detect proxy header : http:192.168.0.63:8118 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] http://www.baidu.com/ use proxy DIRECT:null [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] https://www.google.com/ use proxy SOCKS:/192.168.0.63:1080 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] https://www.google.com/ use proxy SOCKS:/192.168.0.63:1080 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] https://www.google.com/ use proxy HTTP:/192.168.0.63:8118 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] https://www.google.com/ use proxy HTTP:/192.168.0.63:8118 [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] http://www.baidu.com/ use proxy DIRECT:null [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] http://www.baidu.com/ use proxy DIRECT:null [20-12-06 15:23 DEBUG cn.yogehaoren.SwitchProxySelector] https://www.google.com/ use proxy HTTP:/192.168.0.63:8118 http://www.baidu.com/ 连接成功-http://www.baidu.com/ http://www.baidu.com/ 连接成功-http://www.baidu.com/ http://www.baidu.com/ 连接成功-http://www.baidu.com/ https://www.google.com/ 连接成功-https://www.google.com/ https://www.google.com/ 连接成功-https://www.google.com/ https://www.google.com/ 连接成功-https://www.google.com/ https://www.google.com/ 连接成功-https://www.google.com/ https://www.google.com/ 连接成功-https://www.google.com/ ``` 根据测试,多线程并发的情况下,proxy代理并不会相互影响。