本文基於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();
//...
}
直接看到httpCodec.openResponseBody(response)
@Override public ResponseBody openResponseBody(Response response) throws IOException {
streamAllocation.eventListener.responseBodyStart(streamAllocation.call);
String contentType = response.header("Content-Type");
if (!HttpHeaders.hasBody(response)) {//一般情況下都是走到這個分支
Source source = newFixedLengthSource(0);
return new RealResponseBody(contentType, 0, Okio.buffer(source));
}
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
Source source = newChunkedSource(response.request().url());
return new RealResponseBody(contentType, -1L, Okio.buffer(source));
}
long contentLength = HttpHeaders.contentLength(response);
if (contentLength != -1) {
Source source = newFixedLengthSource(contentLength);
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
}
return new RealResponseBody(contentType, -1L, Okio.buffer(newUnknownLengthSource()));
}
根據不同類型創建不同的流去讀請求體數據,一般情況走的一個分支,newFixedLengthSource(0),跟前面創建Sink差不多的流程,這裏是包裝了一下source加了點功能,然後傳入到了RealResponseBody對象中,這裏body對象就創建好了,那麼Response也就創建好了。
public final class RealResponseBody extends ResponseBody {
private final @Nullable String contentTypeString;
private final long contentLength;
private final BufferedSource source;
public RealResponseBody(
@Nullable String contentTypeString, long contentLength, BufferedSource source) {
this.contentTypeString = contentTypeString;
this.contentLength = contentLength;
this.source = source;
}
@Override public MediaType contentType() {
return contentTypeString != null ? MediaType.parse(contentTypeString) : null;
}
@Override public long contentLength() {
return contentLength;
}
@Override public BufferedSource source() {
return source;
}
}
而傳入的source則通過成員變量存儲了起來,直到我們調用ResponseBody#string()方法的時候纔將響應體數據讀出
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);//從流中讀取數據
} finally {
Util.closeQuietly(source);//關閉流
}
}
實際也就是從source中讀取數據,並且讀完後它調用了source#close將流關閉了,所以當我們多次執行ResponseBody#string()的時候會拋出異常因爲流已經關閉了。
2. 總結
CallServerInterceptor內部就是通過ConnectInterceptor創建的HttpCodec與遠端進行io操作,流程上就是四步
- 寫請求頭
- 寫請求體
- 讀響應頭
- 讀響應體
也沒啥可說的了就醬,okhttp全部攔截器終於看完了(^-^)V