前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

OkHttp

原创
作者头像
大发明家
发布2021-12-15 15:21:14
7510
发布2021-12-15 15:21:14
举报
文章被收录于专栏:技术博客文章
代码语言:txt
复制
//JDK : ProxySelector
代码语言:txt
复制
try {
代码语言:txt
复制
    URI uri = new URI("http://restapi.amap.com");
代码语言:txt
复制
    List<Proxy> proxyList = ProxySelector.getDefault().select(uri);
代码语言:txt
复制
    System.out.println(proxyList.get(0).address());
代码语言:txt
复制
    System.out.println(proxyList.get(0).type());
代码语言:txt
复制
} catch (URISyntaxException e) {
代码语言:txt
复制
    e.printStackTrace();
代码语言:txt
复制
}

因此,如果我们不需要自己的App中的请求走代理,则可以配置一个 proxy(Proxy.NO_PROXY) ,这样也可以避免被 抓包。 NO_PROXY

的定义如下:

代理在Java中对应的抽象类有三种类型:

代码语言:txt
复制
public static enum Type {
代码语言:txt
复制
   DIRECT
代码语言:txt
复制
   HTTP,
代码语言:txt
复制
   SOCKS;
代码语言:txt
复制
   private Type() {
代码语言:txt
复制
   } 
代码语言:txt
复制
}

DIRECT :无代理, HTTP :http代理, SOCKS :socks代理。第一种自然不用多说,而Http代理与Socks代理有什 么区别?

对于Socks代理,在HTTP的场景下,代理服务器完成TCP数据包的转发工作; 而Http代理服务器,在转发数据之

外,还会解析HTTP的请求及响应,并根据请求及响应的内容做一些处理。

RealConnection 的 connectSocket 方法:
代码语言:txt
复制
//代理则 new Socket(proxy); 否则无代理或http代理就
代码语言:txt
复制
//address.socketFactory().createSocket(),相当于直接:new Socket()
代码语言:txt
复制
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
代码语言:txt
复制
                ? address.socketFactory().createSocket()
代码语言:txt
复制
                : new Socket(proxy);
代码语言:txt
复制
//connect方法 socket.connect(address);

设置了SOCKS代理的情况下,创建Socket时,为其传入proxy,写代码时连接时还是以HTTP服务器为目标地址(实

际上Socket肯定是与SOCKS代理服务器连);但是如果设置的是Http代理,创建的Socket是与Http代理服务器建 立连接。

在 connect 方法时传递的 address 来自于下面的集合 inetSocketAddresses RouteSelector 的

resetNextInetSocketAddress 方法:

代码语言:txt
复制
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
代码语言:txt
复制
    // ......
代码语言:txt
复制
if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) { //无代理和socks代理,使用http服务器域名与端口
代码语言:txt
复制
      socketHost = address.url().host();
代码语言:txt
复制
      socketPort = address.url().port();
代码语言:txt
复制
    } else {
代码语言:txt
复制
      SocketAddress proxyAddress = proxy.address();
代码语言:txt
复制
      if (!(proxyAddress instanceof InetSocketAddress)) {
代码语言:txt
复制
        throw new IllegalArgumentException(
代码语言:txt
复制
            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
代码语言:txt
复制
      }
代码语言:txt
复制
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
代码语言:txt
复制
      socketHost = getHostString(proxySocketAddress);
代码语言:txt
复制
      socketPort = proxySocketAddress.getPort();
代码语言:txt
复制
}
代码语言:txt
复制
// ......
代码语言:txt
复制
if (proxy.type() == Proxy.Type.SOCKS) {
代码语言:txt
复制
//socks代理 connect http服务器 (DNS没用,由代理服务器解析域名)
代码语言:txt
复制
      inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
代码语言:txt
复制
    } else {
代码语言:txt
复制
//无代理,dns解析http服务器
代码语言:txt
复制
//http代理,dns解析http代理服务器
代码语言:txt
复制
List<InetAddress> addresses = address.dns().lookup(socketHost); //......
代码语言:txt
复制
for (int i = 0, size = addresses.size(); i < size; i++) {
代码语言:txt
复制
        InetAddress inetAddress = addresses.get(i);
代码语言:txt
复制
        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
代码语言:txt
复制
}
代码语言:txt
复制
}
代码语言:txt
复制
}

设置代理时,Http服务器的域名解析会被交给代理服务器执行。但是如果是设置了Http代理,会对Http代理服务器 的域名使用 OkhttpClient

配置的dns解析代理服务器,Http服务器的域名解析被交给代理服务器解析。

上述代码就是代理与DNS在OkHttp中的使用,但是还有一点需要注意,Http代理也分成两种类型:普通代理与隧 道代理。

其中普通代理不需要额外的操作,扮演「中间人」的角色,在两端之间来回传递报文。这个“中间人”在收到客户端

发送的请求报文时,需要正确的处理请求和连接状态,同时向服务器发送新的请求,在收到响应后,将响应结果包

装成一个响应体返回给客户端。在普通代理的流程中,代理两端都是有可能察觉不到"中间人“的存在。

但是隧道代理不再作为中间人,无法改写客户端的请求,而仅仅是在建立连接后,将客户端的请求,通过建立好的 隧道,无脑的转发给终端服务器。隧道代理需要发起Http

CONNECT请求,这种请求方式没有请求体,仅供代理服 务器使用,并不会传递给终端服务器。请求头

部分一旦结束,后面的所有数据,都被视为应该转发给终端服务器的 数据,代理需要把他们无脑的直接转发,直到从客户端的 TCP 读通道关闭。CONNECT

的响应报文,在代理服务 器和终端服务器建立连接后,可以向客户端返回一个 200 Connect established 的状态码,以此表示和终端服务

器的连接,建立成功。

RealConnection的connect方法
代码语言:txt
复制
if (route.requiresTunnel()) {
代码语言:txt
复制
   connectTunnel(connectTimeout, readTimeout, writeTimeout, call,eventListener);
代码语言:txt
复制
   if (rawSocket == null) { 
代码语言:txt
复制
         // We were unable to connect the tunnel but properly closed down our
代码语言:txt
复制
         // resources. 
代码语言:txt
复制
         break;
代码语言:txt
复制
  }
代码语言:txt
复制
} else {
代码语言:txt
复制
   connectSocket(connectTimeout, readTimeout, call, eventListener);
代码语言:txt
复制
} 

requiresTunnel 方法的判定为:当前请求为https并且存在http代理,这时候 connectTunnel 中会发起:

代码语言:txt
复制
CONNECT xxxx HTTP/1.1
代码语言:txt
复制
Host: xxxx
代码语言:txt
复制
Proxy-Connection: Keep-Alive
代码语言:txt
复制
User-Agent: okhttp/${version}

的请求,连接成功代理服务器会返回200;如果返回407表示代理服务器需要鉴权(如:付费代理),这时需要在请求 头中加入 Proxy-

Authorization :

代码语言:txt
复制
  Authenticator authenticator = new Authenticator() {
代码语言:txt
复制
       @Nullable
代码语言:txt
复制
       @Override
代码语言:txt
复制
       public Request authenticate(Route route, Response response) throws IOException {
代码语言:txt
复制
                  if(response.code == 407){
代码语言:txt
复制
                  //代理鉴权
代码语言:txt
复制
                  String credential = Credentials.basic("代理服务用户名", "代理服务密码"); return response.request().newBuilder()
代码语言:txt
复制
                        .header("Proxy-Authorization", credential)
代码语言:txt
复制
                        .build();
代码语言:txt
复制
                  }
代码语言:txt
复制
                  return null;
代码语言:txt
复制
             }
代码语言:txt
复制
       };
代码语言:txt
复制
  new OkHttpClient.Builder().proxyAuthenticator(authenticator);

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • RealConnection 的 connectSocket 方法:
  • RealConnection的connect方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档