OkHttp原理第八篇-CallServerInterceptor

作者简介:00后,22年刚刚毕业,一枚在鹅厂搬砖的程序员。

前置任务:在阅读本篇文章之前希望读者已经阅读了上篇文章OkHttp原理第七篇-ConnectInterceptor,本篇文章详细对CallServerInterceptor进行解析,也希望读者在阅读之前已经对其进行了简单研究。

学习目标:学习CallServerInterceptor如何处理网络流。

创作初衷:学习OkHttp的原理,阅读Kotlin框架源码,提高自己对Kotlin代码的阅读能力。为了读代码而读代码,笔者知道这是不对的,但作为应届生,提高阅读源码的能力笔者认为还是很重要的。


OkHttp原理第八篇-CallServerInterceptor

整体来说CallServerInterceptor的逻辑是比较简单的,主要处理数据流的发送和接收。

经历了这么多篇文章,重点还是分析其intercept()方法

override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()
	// 发送请求头,在上个拦截器ConnectInterceptor中我们已经知道Exchange的codec属性是指向输入输出流的因此下面方法的本质也是调用codec的方法去完成流的操作,具体分析看下1.(Exchange#writeRequestHeaders)
    exchange.writeRequestHeaders(request)

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    // 判断请求方法是否支持请求体,看下(2.HttpMethod#permitsRequestBody)
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
        // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
        // Continue" response before transmitting the request body. If we don't get that, return
        // what we did get (such as a 4xx response) without ever transmitting the request body.
        // 请求头中存在Expect: 100-continue,此字段意味着先往服务器发送请求头,若服务器返回100则继续发送请求体,目的是询问服务器是否可以接受此次请求体,比如请求体过大时,需要先询问服务器是否接收
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
            // 刷新缓冲区,将缓冲区的内容发送到对端
            exchange.flushRequest()
            // 读取响应头并根据响应头的响应码构建ResponseBuilder,若服务器返回100则responseBuilder为null
            // 具体解析看下(3.Exchange#readResponseHeaders)
            responseBuilder = exchange.readResponseHeaders(expectContinue = true)
            exchange.responseHeadersStart()
            invokeStartEvent = false
        }
        // responseBuilder为null有两种情况
        // 1.存在请求体,但是请求头不存在Expect: 100-continue,此时需要立即发送请求头
        // 2.存在请求头,且请求头存在Expect: 100-continue,意味着命中了上面的if,此时responseBuilder为null意味着服务端返回了100响应码
        if (responseBuilder == null) {
            // 是否支持双工通信,双工通信并不是HTTP标准,需要程序员重写RequestBody并覆盖isDuplex()返回true才会命中if,默认情况下不会命中此分支
            if (requestBody.isDuplex()) {
                // Prepare a duplex body so that the application can send a request body later.
                exchange.flushRequest()
                // 发送请求体,createRequestBody()方法看下(4.Exchange#createRequestBody)
                val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
                // 写入输出流中发送数据
                requestBody.writeTo(bufferedRequestBody)
            } else {
                // 将请求体发送到服务器
                // Write the request body if the "Expect: 100-continue" expectation was met.
                val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
                requestBody.writeTo(bufferedRequestBody)
                bufferedRequestBody.close()
            }
        } else {
            // 命中此分支意味着请求头存在Expect: 100-continue,且服务器返回的响应码并不是100,对于此种情况OkHttp则认为请求体是无效的
            exchange.noRequestBody()
            if (!exchange.connection.isMultiplexed) {
                // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
                // from being reused. Otherwise we're still obligated to transmit the request body to
                // leave the connection in a consistent state.
                exchange.noNewExchangesOnConnection()
            }
        }
    } else {
        // 命中此分支意味着请求方法不支持请求体或者请求体本身就不存在
        exchange.noRequestBody()
    }
	// 结束请求
    if (requestBody == null || !requestBody.isDuplex()) {
        exchange.finishRequest()
    }
    // 开始读取真正的响应,之前在请求头存在Expect: 100-continue时读取的响应是服务器是否允许客户端继续上传的答复,并不包含真正的数据 
    // 不命中下述if只有一种情况,responseBuilder仅在请求头存在Expect: 100-continue且服务器返回非100响应码时才不为null,对于OkHttp而言只要是其他响应码则意味着是有效的响应
    if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
            exchange.responseHeadersStart()
            invokeStartEvent = false
        }
    }
    var response = responseBuilder
    .request(request)
    .handshake(exchange.connection.handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build()
    var code = response.code
    // 此处响应码为100就很怪异了,只可能是一种情况,即使我们没有请求,服务器也发送了 100-continue。再次尝试读取实际响应状态。
    if (code == 100) {
        // 在响应码为100的情况下再次读取响应头,此时响应头是真实包含数据的响应的头
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
            exchange.responseHeadersStart()
        }
        response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
        code = response.code
    }

    exchange.responseHeadersEnd(response)
	// 若响应码为101,则意味着需要切换协议,若请求头如下:
    // HTTP/1.1 101 Switching Protocols
    // Upgrade: websocket
    // Connection: Upgrade
    // 则需要将协议切换到websocket
    response = if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        response.newBuilder()
        .body(EMPTY_RESPONSE)
        .build()
    } else {
        // 若是正常的HTTP协议则读取响应体数据
        response.newBuilder()
        .body(exchange.openResponseBody(response))
        .build()
    }
    // 若响应头的Connection字段为close则需要立即关闭此次连接
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)) {
        exchange.noNewExchangesOnConnection()
    }
    // 204,205表明服务端没有数据,若响应码为204,205且响应体有数据则抛出协议异常
    if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
        throw ProtocolException(
            "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
    }
    return response
}

1.Exchange#writeRequestHeaders

fun writeRequestHeaders(request: Request) {
    try {
        eventListener.requestHeadersStart(call)
        // 重点代码具体codec有两个实现类,Http1ExchangeCodec和Http2ExchangeCodec
        codec.writeRequestHeaders(request)
        eventListener.requestHeadersEnd(call, request)
    } catch (e: IOException) {
        eventListener.requestFailed(call, e)
        trackFailure(e)
        throw e
    }
}

Http1ExchangeCodec#writeRequestHeaders

override fun writeRequestHeaders(request: Request) {
    val requestLine = RequestLine.get(request, connection.route().proxy.type())
    writeRequest(request.headers, requestLine)
}

fun writeRequest(headers: Headers, requestLine: String) {
    check(state == STATE_IDLE) { "state: $state" }
    sink.writeUtf8(requestLine).writeUtf8("\r\n")
    // 循环遍历头字段,逐个写入
    for (i in 0 until headers.size) {
        sink.writeUtf8(headers.name(i))
        .writeUtf8(": ")
        .writeUtf8(headers.value(i))
        .writeUtf8("\r\n")
    }
    sink.writeUtf8("\r\n")
    state = STATE_OPEN_REQUEST_BODY
}

Http2ExchangeCodec#writeRequestHeaders

对于HTTP2不进行很深的解析了,输入主要是报文格式的不同,由于HTTP2对头的压缩处理因此和HTTP1会存在区别。

override fun writeRequestHeaders(request: Request) {
    if (stream != null) return

    val hasRequestBody = request.body != null
    val requestHeaders = http2HeadersList(request)
    stream = http2Connection.newStream(requestHeaders, hasRequestBody)
    // We may have been asked to cancel while creating the new stream and sending the request
    // headers, but there was still no stream to close.
    if (canceled) {
        stream!!.closeLater(ErrorCode.CANCEL)
        throw IOException("Canceled")
    }
    stream!!.readTimeout().timeout(chain.readTimeoutMillis.toLong(), TimeUnit.MILLISECONDS)
    stream!!.writeTimeout().timeout(chain.writeTimeoutMillis.toLong(), TimeUnit.MILLISECONDS)
}

2.HttpMethod#permitsRequestBody

如果请求方法为GET或者HEAD则不允许使用请求体

@JvmStatic // Despite being 'internal', this method is called by popular 3rd party SDKs.
fun permitsRequestBody(method: String): Boolean = !(method == "GET" || method == "HEAD")

3.Exchange#readResponseHeaders

fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    try {
        // 与1.Exchange#writeRequestHeaders中类似都是调用codec的方法
        val result = codec.readResponseHeaders(expectContinue)
        result?.initExchange(this)
        return result
    } catch (e: IOException) {
        eventListener.responseFailed(call, e)
        trackFailure(e)
        throw e
    }
}

Http1ExchangeCodec#readResponseHeaders

override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    ...
    try {
        // 读取响应头
        val statusLine = StatusLine.parse(headersReader.readLine())
		// 根据响应头的字符串构建responseBuilder
        val responseBuilder = Response.Builder()
        .protocol(statusLine.protocol)
        .code(statusLine.code)
        .message(statusLine.message)
        .headers(headersReader.readHeaders())
        return when {
            // 若expectContinue为true,且100则返回null,意味着需要发送请求体
            expectContinue && statusLine.code == HTTP_CONTINUE -> {
                null
            }
            // 客户端未发起请求服务端返回了100,意味着需要再次读取响应头
            statusLine.code == HTTP_CONTINUE -> {
                state = STATE_READ_RESPONSE_HEADERS
                responseBuilder
            }
            else -> {
                state = STATE_OPEN_RESPONSE_BODY
                responseBuilder
            }
        }
    } catch (e: EOFException) {
        ...
    }
}

Http2ExchangeCodec#readResponseHeaders

override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    val headers = stream!!.takeHeaders()
    val responseBuilder = readHttp2HeadersList(headers, protocol)
    return if (expectContinue && responseBuilder.code == HTTP_CONTINUE) {
        null
    } else {
        responseBuilder
    }
}

4.Exchange#createRequestBody

此方法返回一个输出流,等后续往输出流中输出数据才会真正将数据发送,由于输出的格式不同因此需要不同的输出流支持

fun createRequestBody(request: Request, duplex: Boolean): Sink {
    this.isDuplex = duplex
    val contentLength = request.body!!.contentLength()
    eventListener.requestBodyStart(call)
    val rawRequestBody = codec.createRequestBody(request, contentLength)
    // 返回输出流
    return RequestBodySink(rawRequestBody, contentLength)
}

Http1ExchangeCodec#createRequestBody

override fun createRequestBody(request: Request, contentLength: Long): Sink {
    return when {
        request.body != null && request.body.isDuplex() -> throw ProtocolException(
            "Duplex connections are not supported for HTTP/1")
        // http1.1中的分块传输,在请求头包含Transfer-Encoding:chunked时触发,每块以“\r\n”开始
        request.isChunked -> newChunkedSink() // Stream a request body of unknown length.
        // 若存在长度,则返回一个以长度未传输规则的输出流
        contentLength != -1L -> newKnownLengthSink() // Stream a request body of a known length.
        else -> // Stream a request body of a known length.
        throw IllegalStateException(
            "Cannot stream a request body without chunked encoding or a known content length!")
    }
}

Http2ExchangeCodec#createRequestBody

override fun createRequestBody(request: Request, contentLength: Long): Sink {
    return stream!!.getSink()
}

5.Exchange#openResponseBody

和写入数据一样,返回一个输入流,当我们真正读取数据时才会读取此输入流当中的数据,目前并没有读取数据。

fun openResponseBody(response: Response): ResponseBody {
    try {
        val contentType = response.header("Content-Type")
        val contentLength = codec.reportedContentLength(response)
        val rawSource = codec.openResponseBodySource(response)
        val source = ResponseBodySource(rawSource, contentLength)
        return RealResponseBody(contentType, contentLength, source.buffer())
    } catch (e: IOException) {
        eventListener.responseFailed(call, e)
        trackFailure(e)
        throw e
    }
}

Http1ExchangeCodec#openResponseBodySource

override fun openResponseBodySource(response: Response): Source {
  return when {
    !response.promisesBody() -> newFixedLengthSource(0)
    response.isChunked -> newChunkedSource(response.request.url)
    else -> {
      val contentLength = response.headersContentLength()
      if (contentLength != -1L) {
        newFixedLengthSource(contentLength)
      } else {
        newUnknownLengthSource()
      }
    }
  }
}

Http2ExchangeCodec#openResponseBodySource

override fun openResponseBodySource(response: Response): Source {
    return stream!!.source
}

HTTP协议知识总结

在最后一个拦截器中,照样离不开HTTP协议的内容

Expect:100-continue

Expect 是一个请求消息头,包含一个期望条件,表示服务器只有在满足此期望条件的情况下才能妥善地处理请求。

  • 100 如果消息头中的期望条件可以得到满足,使得请求可以顺利进行的话,
  • 417 (Expectation Failed) 如果服务器不能满足期望条件的话;也可以是其他任意表示客户端错误的状态码(4xx)。

如果请求中 Content-Length 的值太大的话,可能会遭到服务器的拒绝。

响应码 101

HTTP 101 Switching Protocol(协议切换)状态码表示服务器应客户端升级协议的请求,比如由HTTP切换到WebSocket

响应码 204

HTTP **204 No Content **成功状态响应码,表示该请求已经成功了,但是客户端客户不需要离开当前页面。使用情况如下:在 PUT 请求中进行资源更新,但是不需要改变当前展示给用户的页面,那么返回 204 No Content。如果创建了资源,则返回 201 Created 。如果应将页面更改为新更新的页面,则应改用 200

响应码 205

在 HTTP 协议中,响应状态码 205 Reset Content 用来通知客户端重置文档视图,比如清空表单内容、重置 canvas 状态或者刷新用户界面。

总结

相对于其他拦截器来说CallServerInterceptor简直简单的不能再简单了,只是间接调用编码器的一些方法对数据进行接收和输出,其逻辑主要涉及到100响应码的处理,对于100响应码在HTTP协议知识总结小节已经很清楚了

参考文献 : https://developer.mozilla.org/en-US/docs/Web/HTTP

原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!}

⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!}

✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

OkHttp系列更新结束撒花,未来有机会可能还会更新,比如WebSocket

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值