✨作者简介:00后,22年刚刚毕业,一枚在鹅厂搬砖的程序员。
✨前置任务:在阅读本篇文章之前希望读者已经阅读了上篇文章OkHttp原理第七篇-ConnectInterceptor,本篇文章详细对
CallServerInterceptor
进行解析,也希望读者在阅读之前已经对其进行了简单研究。
✨学习目标:学习
CallServerInterceptor
如何处理网络流。
✨创作初衷:学习
OkHttp
的原理,阅读Kotlin
框架源码,提高自己对Kotlin
代码的阅读能力。为了读代码而读代码,笔者知道这是不对的,但作为应届生,提高阅读源码的能力笔者认为还是很重要的。
整体来说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协议的内容
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