连接拦截器
-
本文概述:
- 文章以OkHttp 中的连接拦截器为主题,讨论了其工作时机、工作内容、initExchange 干了什么、OkHttp 是怎么建立Socket连接、建立好Socket 连接后,我们怎么知道是发起HTTP1.X 还是HTTP2.0呢、补充了ALPN 协议、补充了代理连接;
什么时候工作?
- 请求经过缓存拦截器处理后,不满足使用强缓存的条件(需要使用协商缓存或者其他缓存),此时就会将请求下发到连接拦截器;
主要干什么:
- 帮助获取连接对象
源码长什么样子:ConnectInterceptor
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 获取连接 Exchange对象:进行数据交换(可以看成一个连接)
//利用了第一个拦截器( 重试重定向拦截器)在Call 身上创建的去获取连接的一个对象去完成连接的获取与查找
val exchange = realChain.call.initExchange(chain)
//根据exchange 去创建一个新的链条对象,去执行最后一个拦截器
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
initExchange 干了什么?
-
获取编解码器对象:将请求封装为Http 报文,将响应解析为OkHttp 中的Response 对象
//ExchangeCodec: 编解码器 find:查找连接Realconnection val codec = exchangeFinder.find(client, chain)- find 方法是怎么去查到并获取一个连接,又是怎么确定编解码器使用的是Http 1 还是Http 2?
-
封装数据交换器对象:initExchange 最后就会返回出这个对象
// Exchange:数据交换器 包含了exchangecodec与Realconnection val result = Exchange(this, eventListener, exchangeFinder, codec)
-
find 方法干了什么?
-
内部存在findHealthyConnection ,在其中就完成了连接的查找与获取
val resultConnection = findHealthyConnection( connectTimeout = chain.connectTimeoutMillis, readTimeout = chain.readTimeoutMillis, writeTimeout = chain.writeTimeoutMillis, pingIntervalMillis = client.pingIntervalMillis, connectionRetryEnabled = client.retryOnConnectionFailure, doExtensiveHealthChecks = chain.request.method != "GET" )-
findHealthyConnection 干了什么?
-
执行findConnection :去获取连接
-
判断连接是否健康:判断Sokect 是否关闭等
- 因为OkHttp 使用的是Http 协议,Http 协议使用的是TCP/IP,而底层是使用Socket 协议;
if (rawSocket.isClosed || socket.isClosed || socket.isInputShutdown || socket.isOutputShutdown) { return false }
-
-
findConnection 是怎么去找到连接的?
-
首先判断连接是否被取消
if (call.isCanceled()) throw IOException("Canceled") -
拿到连接
//这个Call 是AsyncCall 的一个对象,connection 为其类属性代表一个连接 val callConnection = call.connection -
判断连接的非空性?
- 第一次请求肯定是null ,后面进行重定向后可能就不为null
-
当连接为空:从连接池中找有没有现成的与本次请求目标服务器相同的连接,有就直接拿来用
//连接池 查找 连接 if (connectionPool.callAcquirePooledConnection(address, call, null, false)) { val result = call.connection!! eventListener.connectionAcquired(call, result) return result }- 没有:创建RealConnection 对象
val newConnection = RealConnection(connectionPool, route)-
调用connect 建立连接:Socket
try { newConnection.connect( …… )
-
-
findHealthyConnection 与 findConnection 有什么区别?
- 后者获取的连接不一定是健康的,也就是说这个连接可能与服务器已经断开了 ;连接健康(能传数据)
-
-
利用连接(resultConnection) 去创建ExchangeCodec
return resultConnection.newCodec(client, chain)-
newCodec 干了什么?
- 根据RealCall 连接对象做了协议的判断
return if (http2Connection != null) { Http2ExchangeCodec(client, this, chain, http2Connection) }
-
-
OkHttp 是怎么建立Socket连接的:调用RealCall 对象的connect
-
在while (true) 中
-
建立隧道代理
if (route.requiresTunnel()) {- connectSocket:最终调用socket.connect
- 先发connect 请求 建立隧道代理
val requestLine = "CONNECT ${url.toHostHeader(includeDefaultPort = true)} HTTP/1.1" -
建立HTTP 普通/Socket 代理
else{
-
建立好Socket 连接后,我们怎么知道是发起HTTP1.X 还是HTTP2.0呢?
-
OkHttp 在完成连接后会记录所使用的HTTP 版本信息
connectTls(connectionSpecSelector) -
connectTls:SSL 握手(在这个地方就会确认所使用的HTTP 版本信息)
//maybeProtocol 的非空性决定所使用的HTTP 协议版本 // Success! Save the handshake and the ALPN protocol. val maybeProtocol = if (connectionSpec.supportsTlsExtensions) { Platform.get().getSelectedProtocol(sslSocket) } //默认使用HTTP 1.1 protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1 -
关键代码:getSelectedProtocol
-
从sslSocket 中拿到当前所使用的HTTP 协议版本信息
-
在握手的时候已经确认了所使用的HTTP 版本信息
-
底层:Android 的实现
- 反射调用sslSocket 中的一个方法,得到返回值alpnResult (这个里面就有所使用的HTTP 版本信息)
-
-
补充ALPN 协议:应用层协议协商扩展(确定使用的HTTP 版本信息)
- 是TLS (SSL)的扩展协议,SSL 握手的时候会去确认所使用的加密方式,协议版本号,证书校验等;
- SSL 握手第一步:客户端发送Client Hello(包含了ALPN ---> 里面就有所支持的HTTP 版本信息),服务器在回传响应(Server Hello)的时候,里面包含ALPN ---> 指定了所使用的HTTP 协议版本信息
代理连接相关补充:
-
概述:在建立Socket 连接的时候就可以配置代理
-
Socket 代理连接
- 代理的是Sokect(TCP/IP ),可以完成TCP/IP 协议所有的代理(RTMP,RTSP)
- 代理服务器连接的是真实服务器
-
HTTP 代理连接
-
后者代理的是(HTTP ),只能代理(HTTP 代理)
-
HTTP普通代理:
-
connect 的是HTTP代理服务器并不是真实的服务器
-
发起请求 (get 请求) 时,需要在请求行的Path 中附带域名(这样代理服务端才知道要请求谁)
-
原始请求行:
- 请求方法,Path,协议版本号
-
-
-
HTTP隧道代理:建立连接部分跟普通HTTP 代理相同
- 在真正进行数据请求前,需要先发connect 请求(包含真实服务端的地址)
-
-
-
示意图:
-
代理是怎么工作的:C <---> S
- 起到一个中间人的作用,C 端将请求交给代理,代理再交给S 端;S 端将响应交给代理,代理再将响应交给C 端
-
细节:如果使用的是HTTPS,那么此时使用的是HTTP 隧道代理
-
隧道代理与普通代理有什么区别?
-
普通代理:充当中间人
- 在拿到C 端请求后,可以对其做一定处理,然后交给S 端
-
隧道代理:不再充当中间人
- 不能修改客户端请求,无脑转发给服务端;且其需要先发起Http CONNECT 请求(没有请求体,仅提供给代理服务器使用,并且不会传给服务端)
-
-
OkHttp 是可以允许配置HTTP 代理的
- 基本上找不到免费的HTTP 代理服务器
var okHttpClient = OkHttpClient.Builder() //配置Http 代理 .proxy(Proxy.Type.HTTP,InetSocketAddress("HTTP 代理服务器域名","端口号"))- 测试
//okhttp的用法,还可以 //Socket 指定代理服务器 Socket socket = new Socket(new Proxy( Proxy.Type.HTTP, new InetSocketAddress( "124.205.155.148",9090))); //Socket 指定真实服务器 socket.connect(new InetSocketAddress("restapi.amap.com", 80)); System.out.println("连接完成!"); //照常请求: StringBuilder sb = new StringBuilder(); sb.append("GET /v3/weather/weatherInfo?city=长沙&key" + "=13cb58f5884f9749287abbead9c658f2 HTTP/1.1\r\n"); 但是,如果你是直接用Socket对象connect代理服务器,那么在后续请求中,需要在Path处加上域名 如果是HTTPS ,需要在真正请求之前先发起一次connect 请求,当服务端响应(表示连接建立成功)才能去发起真正请求 -
OkHttp 同样可以配置Socket 代理
-
启动Socket 代理服务端
-
创建Socket 对象:需要指定代理服务器IP 与 端口
Socket socket = new Socket(new Proxy( Proxy.Type.SOCKS, new InetSocketAddress( "localhost",808))); -
调用connect:传入真正目标服务器的IP 与 端口
//Socket 指定真实服务器 socket.connect(new InetSocketAddress("restapi.amap.com", 80)); -
然后就正常地使用就行了
-
Socket 代理服务器
- 可以拿到用户的请求并解析,还可以拿到服务器的响应进行处理;甚至还可以处理指定的握手轮次
-