在开始之前拓展一个http的知识:
1、关键字100-continue介绍
http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,则POST上传数据。在现实应用中,通过在POST大数据时,才会使用100-continue协议。
2、客户端策略
- 如果客户端有POST数据要上传,可以考虑使用100-continue协议。加入头{"Expect":"100-continue"}
- 如果没有POST数据,不能使用100-continue协议,因为这会让服务端造成误解。
- 并不是所有的Server都会正确实现100-continue协议,如果Client发送Expect:100-continue消息后,在timeout时间内无响应,Client需要立马上传POST数据。
- 有些Server会错误实现100-continue协议,在不需要此协议时返回100,此时客户端应该忽略。
3、服务端策略
- 正确情况下,收到请求后,返回100或错误码。
- 如果在发送100-continue前收到了POST数据(客户端提前发送POST数据),则不发送100响应码(略去)。
回到CallServerInterceptor核心Intercept方法:
@Override public Response intercept(Chain chain) throws IOException {
...
//封装请求头,即conent-length,method,编码等等
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
//当前request需要请求体,即post请求等方式,如果有,则进行封装
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 判断服务器是否允许发送body
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// 向服务器发送requestbody,
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
....
}
//结束请求
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//返回response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//100的状态码的处理继续发送请求,继续接受数据
int code = response.code();
if (code == 100) {
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
...
//返回为空的处理
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
简单概述一下流程:
1、获取到 ConnectInterceptor 拦截器中的httpCodec和realChain,至于ConnectInterceptor中做了什么,可以看之前写的《Okhttp之ConnectInterceptor拦截器原理及解析》
2、写入http请求头信息,判断是否允许上传requestBody
判断header中Expect域是否为100-continue,这个请求头字段的作用是在发送RequestBody前向服务器确认是否接受RequestBody,如果没有,则正常请求。如果有,则相当于一次简单的握手操作,则等待服务器返回的ResponseHeaders之后再继续,如果服务器接收RequestBody,会返回null。
根据3的判断,如果RequestBuilder为null,说明Expect不为100-continue或者服务器同意接收RequestBody。这时就开始向流中写入RequestBody。
读取响应头信息 构建Response,写入原请求、握手情况、请求时间、得到结果的时间等
针对204/205状态码处理
返回Response