1. Http2.0 相比Http1.1有哪些改进
- 多路复用,同一个TCP连接可以发送多个http请求。
- 相比http1.1使用文本传输,Http2.0采用了二进制格式,文本的种类繁多要做到全面覆盖足够健壮较复杂,使用二进制格式解析方便且高效。同时二进制数据在一次交互中有流和帧的概念,流有唯一ID,一个流上有一个或多个帧,帧可以乱序发送,在接收端可以重组数据。
例如:流 1 - HTML
- 流标识符:1
- 帧 1(HEADERS 帧):包含请求的头部信息
- 帧 2(DATA 帧):包含 HTML 内容的一部分
- 帧 3(DATA 帧):包含 HTML 内容的另一部分
- Header压缩,由于Header传输的数据key很多都是重复的,没必要每次都传输,在两端通过建立静态表保存固定的字段,动态表保存动态变更的字段,两端传输数据时使用编码器将key转化成表格索引号来执行传输,收到数据的一端再用解码器转换成实际的header key。
- 支持push,服务器主动向客户端发送消息。
2. Http3.0 相比Http2.0有哪些改进
主要是改用UDP来传输数据,解决TCP传输效率低下的问题。 具体参考www.cnblogs.com/wiesslibrar…
3. OKhttp3有哪些默认拦截器
3.1 RetryAndFollowUpInterceptor重试重定向拦截器
主要功能:通过recover()方法来判断是否有必要进行重试,若是一些不可更改的错误,则直接抛出异常,终止此次请求,否则执行下一次循环。源码中有一些无法重试的错误包括tcp错误,非超时连接的断连IO错误等等。
private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
// If there was a protocol problem, don't recover.
if (e is ProtocolException) {
return false
}
// If there was an interruption don't recover, but if there was a timeout connecting to a route
// we should try the next route (if there is one).
if (e is InterruptedIOException) {
return e is SocketTimeoutException && !requestSendStarted
}
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
// again with a different route.
if (e is SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
if (e.cause is CertificateException) {
return false
}
}
if (e is SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
return false
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true
}
重定向逻辑是在拦截器后半段
override fun intercept(chain: Interceptor.Chain): Response {
val exchange = call.interceptorScopedExchange
//followUpRequest() 通过获得响应来判断是否需要进行重定向及具体操作
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
// ...
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
// 如果超过了重定向的最大重试次数,则抛出错误
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
// 重定向的新的request
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
// 如果是308,307,300,301,302,303这些状态码,则重建request
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// ...
}
3.2 BridgeInterceptor
BridgeInterceptor主要的作用是将用户配置的头部信息转换成http请求的头部信息,比如Content-Type,Content-Length,Transfer-Encoding,Host,Connection。同时默认添加了Gzip压缩的Accept-Encoding,再返回的response后也解压出具体的数据
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
// 未配置Accept-Encoding主动配置成Gzip,并负责解压
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
// ...
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
// content-Length一般是分块传输或者压缩传输时无法确定原始长度是传递-1
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
3.3 CacheInterceptor
源码解读可以参考这篇文章: OKHTTP核心之五大内置拦截器源码分析 基本是按照Http缓存策略来处理的,根据头部的配置来读写缓存。
3.3.1 Cache-Control强制缓存策略
no-store,针对变化频率很高的资源,不允许客户端缓存,每次必须访问服务器。no-cache,(很容易误解)实际是允许使用缓存,但是使用前必须和服务器验证,如果返回403表示缓存可用,就可以使用缓存。must-recalidate,表示允许缓存,并且如果缓存不过期的话,先使用缓存,如果缓存过期的话,再去服务器端进行验证max-age=xx,表示缓存过期计时时间,这个是服务端响应报文创建时间为节点,由于网络传输还有一定时间间隔,客户端能实际使用的缓存时间会短一点。
如果同时配置了上述策略,以上述1234优先级顺序来执行策略,比如设置了no-cache和max-age,即使在缓存过期时间内也会去访问服务器确定缓存的有效性。
3.3.2 协商缓存策略:Last-Modified/if-Modified-Since
如果服务器返回了Last-Modified: xxx,表示资源上一次修改的时间,客户端可以记录该时间,再下一次的请求头中添加if-Modified-Since,服务器会根据这个值在对比资源上次修改时间确定返回403还是新资源。
3.3.3 协商缓存策略:ETag/If-None-Match
如果服务器返回了ETag:id,表示返回了上一次资源的唯一ID,客户端可以记录该ID,在下一次请求头中添加If-None-Match:id,服务器会对比资源ID确认缓存有效性。如果同时返回了If-None-Match和if-Modified-Since,优先查看资源ID,因为资源可能改变后又变回来了,修改时间产生了变化但是资源本身其实未变。
3.4 ConnectInterceptor
4. 网络拦截器VS应用拦截器
应用拦截器
- 不需要考虑重定向和重试等中间响应。
- 仅调用一次,即使 http 响应是从缓存中获取的结果。
- 主要关注应用程序的原始意图,不关心 okhttp 注入的头,如 If-None-Match ...
- 允许短路操作,即 不调用 Chain.proceed()。
- 允许重试并多次调用 Chain.proceed()
- 可以使用 withConnectTimeout, withReadTimeout, withWriteTimeout 来调整 Call 超时时间。
网络拦截器
- 能够操作中间处理过程,如重定向和重试。
- 不关注 cache 层拦截的短路操作
- 关注网络层数据传输
- 执行 Connection 请求
参考官网:square.github.io/okhttp/feat…
5. OkHttp拦截器和自定义拦截器执行顺序
直接上源码:RealCall.kt 可以看出用户添加的拦截器会被首先添加,默认拦截器后再后续依次添加。再依据拦截器内部实现是在获取response前执行部分代码,获取之后再执行部分代码,所以基本是一个U型结构,先处理request的后处理response,跟View的拦截器处理模式类似。
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
//...
)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
// ...
盗图盗图: