OkHttp-CallServerInterceptor源码解析
Posted zhuliyuan丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHttp-CallServerInterceptor源码解析相关的知识,希望对你有一定的参考价值。
本文基于okhttp3.10.0,并且只介绍http1相关的内容不涉及http2.0
终于到最后一个拦截器了,前面我们说到在ConnectInterceptor中会建立连接创建RealConnection和HttpCodec,然后传递给下一个拦截器也就是CallServerInterceptor进行网络读写操作,那今天的博客就从这里开始。
1. 源码解析
讲之前简单介绍下HttpCodec,它是对客户端与服务器进行io操作的封装,默认提供了两个实现Http1Codec、Http2Codec,我们只讲http1.0所以只关注Http1Codec即可
public final class Http1Codec implements HttpCodec
final BufferedSource source;
final BufferedSink sink;
内部有source和sink两个成员变量用来进行读写操作,这两个流是在ConnectInterceptor建立连接的时候通过socket创建,所以通过他们就可以与远端进行通信了。
//RealConnection.java
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException //建立连接
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,,
StreamAllocation streamAllocation) throws SocketException //创建HttpCodec
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
ok回到主题,对于CallServerInterceptor中的操作,我们大体可以分为四步
- 写请求头
- 写请求体
- 读响应头
- 读响应体
那源码讲解的时候会将CallServerInterceptor源码做一个拆解,从这四步来分析。
1.1 写请求头
//CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();//获取ConnectInterceptor创建的HttpCodec
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();//发起请求的时间
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);//写请求头
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
...
return response;
在CallServerInterceptor中第一步是先拿到HttpCodec,这个类是我们与server进行io操作的封装类,内部是通过Okio与远端进行读写的。
然后调用到httpCodec#writeRequestHeaders(request)进行请求头的写入
@Override public void writeRequestHeaders(Request request) throws IOException
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());//获取请求行
writeRequest(request.headers(), requestLine);//写入请求头
先获取请求行,然后写入请起头
//RequestLine#get
public static String get(Request request, Proxy.Type proxyType)
StringBuilder result = new StringBuilder();
result.append(request.method());
result.append(' ');
if (includeAuthorityInRequestLine(request, proxyType))
result.append(request.url());
else
result.append(requestPath(request.url()));
result.append(" HTTP/1.1");
return result.toString();
请求行的获取其实就是method url 协议三段拼接起来的,像这样POST /app/a/update HTTP/1.1
public void writeRequest(Headers headers, String requestLine) throws IOException
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\\r\\n");//写入请求行并换行
for (int i = 0, size = headers.size(); i < size; i++) //写入请求头
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\\r\\n");
sink.writeUtf8("\\r\\n");//换行
state = STATE_OPEN_REQUEST_BODY;
先写入请求行,然后在遍历写入请求头,最后写完大概像这样
POST /app/a/update HTTP/1.1
Host: XXX.XXX.XXX.XXX:9999
Content-Type: application/x-www-form-urlencoded
1.2 写请求体
@Override public Response intercept(Chain chain) throws IOException
//...
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) //如果method不为get和head,并且有请求体
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) //100-continue属于特殊的请求头我们可以忽略
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
if (responseBuilder == null)
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);
else if (!connection.isMultiplexed())
streamAllocation.noNewStreams();
httpCodec.finishRequest();//完成请求写入
//...
如果非get、head请求并且有请求体,创建输出流对象,写入请求体。
接下来输出流的创建httpCodec#createRequestBody()
@Override public Sink createRequestBody(Request request, long contentLength)
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) //分块传输的则创建newChunkedSink
return newChunkedSink();
if (contentLength != -1) //普通固定长度的则创建newFixedLengthSink
return newFixedLengthSink(contentLength);
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
public Sink newFixedLengthSink(long contentLength)
if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
state = STATE_WRITING_REQUEST_BODY;
return new FixedLengthSink(contentLength);//创建一个FixedLengthSink对象
根据当前请求是否需要分块传输做区分,一般情况下都是固定长度的创建一个FixedLengthSink
private final class FixedLengthSink implements Sink
private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
private boolean closed;
private long bytesRemaining;
FixedLengthSink(long bytesRemaining)
this.bytesRemaining = bytesRemaining;//记录要传输的数据量
@Override public Timeout timeout()
return timeout;
@Override public void write(Buffer source, long byteCount) throws IOException
if (closed) throw new IllegalStateException("closed");
checkOffsetAndCount(source.size(), 0, byteCount);
if (byteCount > bytesRemaining) //超出传输数据量抛异常
throw new ProtocolException("expected " + bytesRemaining
+ " bytes but received " + byteCount);
sink.write(source, byteCount);
bytesRemaining -= byteCount;
@Override public void flush() throws IOException
if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
sink.flush();
@Override public void close() throws IOException
if (closed) return;
closed = true;
if (bytesRemaining > 0) throw new ProtocolException("unexpected end of stream");
detachTimeout(timeout);
state = STATE_READ_RESPONSE_HEADERS;
内部其实就是对sink的包装,加上了对长度的判断,如果write的数据长度超过了content-lenght则抛出异常,而new CountingSink()
也是包装了一层记录了下传输成功的数据量,这里就不细说了。
再看到请求的写入request.body().writeTo(bufferedRequestBody)
,有多种实现,我们直接看表单的吧
//FormBody.java
@Override public void writeTo(BufferedSink sink) throws IOException
writeOrCountBytes(sink, false);
private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes)
long byteCount = 0L;
Buffer buffer;
if (countBytes)
buffer = new Buffer();
else
buffer = sink.buffer();//获取输出流的buffer
for (int i = 0, size = encodedNames.size(); i < size; i++) //将表单数据写入
if (i > 0) buffer.writeByte('&');
buffer.writeUtf8(encodedNames.get(i));
buffer.writeByte('=');
buffer.writeUtf8(encodedValues.get(i));
if (countBytes)
byteCount = buffer.size();
buffer.clear();
return byteCount;
for循环写入请求体,最后CallServerInterceptor中调用了bufferedRequestBody.close()
将请求体写入。
1.3 读响应头
@Override public Response intercept(Chain chain) throws IOException
//...
if (responseBuilder == null)
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);//获取响应头
Response response = responseBuilder//创建Response
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//...
通过httpCodec#readResponseHeaders(false)获取响应头
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS)
throw new IllegalStateException("state: " + state);
try
StatusLine statusLine = StatusLine.parse(readHeaderLine());//获取响应中第一行数据解析成StatusLine
Response.Builder responseBuilder = new Response.Builder()//创建Response.Builder对象
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());//读取响应头
if (expectContinue && statusLine.code == HTTP_CONTINUE)
return null;
else if (statusLine.code == HTTP_CONTINUE)
state = STATE_READ_RESPONSE_HEADERS;
return responseBuilder;
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
catch (EOFException e)
IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
exception.initCause(e);
throw exception;
读取第一行数据作为响应行
private String readHeaderLine() throws IOException
String line = source.readUtf8LineStrict(headerLimit);
headerLimit -= line.length();
return line;
随后解析为StatusLine对象,然后在readHeaders()读取响应头
public Headers readHeaders() throws IOException
Headers.Builder headers = new Headers.Builder();
for (String line; (line = readHeaderLine()).length() != 0; ) //解析到第一个空行,因为response是用空行来分割响应头和响应体的
Internal.instance.addLenient(headers, line);//给Headers.Builder添加头部属性
return headers.build();
Builder addLenient(String line)
int index = line.indexOf(":", 1);
if (index != -1)
return addLenient(line.substring(0, index), line.substring(index + 1));//获取name和value
else if (line.startsWith(":"))
// Work around empty header names and header names that start with a
// colon (created by old broken SPDY versions of the response cache).
return addLenient("", line.substring(1)); // Empty header name.
else
return addLenient("", line); // No header name.
Builder addLenient(String name, String value) //添加到builder中
namesAndValues.add(name);
namesAndValues.add(value.trim());
return this;
也是一行一行读,直到读到第一个换行为止,因为响应头和响应体之间是通过一个空行来区分。然后创建Header对象传入responseBuilder,最后构建出response。
1.4 读响应体
@Override public Response intercept(Chain chain) throws IOException
//...
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))//读取响应体
.build();
//...以上是关于OkHttp-CallServerInterceptor源码解析的主要内容,如果未能解决你的问题,请参考以下文章