您的位置:首页 > 理论基础 > 计算机网络

OKHttp原码分析(七)之HttpStream

2017-05-07 14:07 204 查看

一,概述

在上篇blog中,讲解了RealConnection类以及重要的方法。当RealConnection类只在httpStream类中被引用。httpStream类是对RealConnection类的封装。

在ConnectInterceptor类中创建了httpStream对象。在CallServerInterceptor类中使用httpstream写请求头,写请求头,得到Response对象。从这也可以看出httpstream类的重要性。

在网络请求中除了httpStream类和RealConnection类还有一个重要的类,StreamAllocation。下面首先看下这个类的创建。

二,StreamAllocation对象的创建

得到StreamAllocation对象的代码是:

StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();


下面看RealInterceptorChain类的streamAllocation方法的源码:

public StreamAllocation streamAllocation() {
return streamAllocation;
}


发现这个方法只是返回了streamAllocation对象,这个对象已经在其他地方被创建了。下面开始寻找这个对象的来源。

通过寻找发现streamAllocation对象在RetryAndFollowUpInterceptor类的intercept方法中被创建,核心代码是:

streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()));


下面看createAddress方法的源码:

private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}

return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}


这儿有个重要的字段需要注意,sslSocketFactory。如果url是https则sslSocketFactory有值。如果url是http则sslSocketFactory等于null.

三,httpstream对象的创建

创建httpstream对象使用的代码是:

HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);


streamAllocation是StreamAllocation 类,newStream方法的源码是:

public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();//得到设置在OKHttpClient中的字段
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();

try {
;//得到RealConnection 对象,这个是建立网络连接的关键代码,后面做详细讲解。
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

HttpStream resultStream;
;//使用RealConnection 对象创建HttpStream对象,
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(client, this,resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(
client, this, resultConnection.source, resultConnection.sink);
}
return resultStream;
} catch (IOException e) {
throw new RouteException(e);
}
}


分析:

在代码中创建httpStream对象时,根据情况使用了两个不同的类,Http1xStream和Http2xStream类。这两个类的作用是一样的,只是使用的协议不同。Http1xStream使用的是http1.1协议,Http2xStream使用的是http2.0或SPDY协议。

究竟是使用Http1xStream对象还是使用Http2xStream对象,要看resultConnection.framedConnection != null的返回值。

在RealConnection类的establishProtocol方法中得知,如果是https则创建framedConnection 对象。即如果是https就使用Http2xStream类创建对象。如果是http则使用Http1xStream创建对象。

其实两个类的作用和用法是类似的,只是协议不同。下面以Http1xStream为例分析HttpStream的使用。首先看Http1xStream的构造方法。

四,Http1xStream的构造方法

public Http1xStream(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source, BufferedSink sink) {
this.client = client;
this.streamAllocation = streamAllocation;
this.source = source;
this.sink = sink;
}


这个方法中尤其注意一点:source对象和sink对象是从RealConnection中传递过来的,即这是链接socket以后得到的输入流和输出流对象。

五,向服务器中写数据的操作

在分析RequestBody类中我们知道写操作的本质调用的是RequestBody的WriteTo方法,但这个方法中需要传递一个输出流对象。下面就看这个方法被调用的地方。

在CallServerInterceptor类的intercept方法中调用了RequestBody的WriteTo方法,源码如下:

if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}


此时传递的是bufferedRequestBody对象,这个对象是由httpStream的createRequestBody方法得到。下面看Http1xStream类的createRequestBody方法的源码:

@Override public Sink createRequestBody(Request request, long contentLength) {
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
// Stream a request body of unknown length.
return newChunkedSink();
}

if (contentLength != -1) {
// Stream a request body of a known length.
return newFixedLengthSink(contentLength);
}

throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}


这个方法中调用了newFixedLengthSink方法,newFixedLengthSink方法中创建了FixedLengthSink对象。

FixedLengthSink是Http1xStream中的内部类,实现了sink接口,并重写了write方法,下面看write方法的源码:

@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;
}


此时发现,FixedLengthSink的write方法调用的是Http1xStream的sink字段的write方法。我们知道Http1xStream的sink字段值来自于socket的输出流。

到目前为止,从RequestBody设置参数,到向服务器写参数都梳理通了。

六,从服务器中得到数据

在分析Response类时,我们知道服务的返回值数据都是从ResponseBody中获取的。那么这个类是什么时候被创建的呢,如下:

在CallServerInterceptor类的intercept方法中有下面代码:

Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();//创建请求头

if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();//设置请求体
}


下面看Http1xStream类的openResponseBody方法的源码:

@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}


在分析RealResponseBody时就知道,创建RealResponseBody必须有一个输入流,只要有输入流就可以从流中解析字节数组数据或字符串数据。得到source 对象使用的是getTransferStream方法。

在getTransferStream方法中调用了newFixedLengthSource方法,newFixedLengthSource方法中创建了FixedLengthSource对象。

FixedLengthSource是Http1xStream中的内部类,继承AbstractSource类,并重写了read方法,下面看read方法的源码:

@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (bytesRemaining == 0) return -1;

long read = source.read(sink, Math.min(bytesRemaining, byteCount));
if (read == -1) {
endOfInput(false); // The server didn't supply the promised content length.
throw new ProtocolException("unexpected end of stream");
}

bytesRemaining -= read;
if (bytesRemaining == 0) {
endOfInput(true);
}
return read;
}


此时发现,FixedLengthSource的read方法调用的是Http1xStream的source字段的read方法。我们知道Http1xStream的source字段值来自于socket的输入流。

到目前为止,从服务器中得到流对象,从流对象中解析数据都梳理通了。

八,OKHttp是怎么区分的响应头与响应体

在socket中是不区分响应头和响应体的,所有数据都放在一个流中。而OKHttp是区分响应头和响应体的。OKHttp底层又是socket来实现的。那么OKHttp是怎么区分的响应头和响应体的呢?

在CallServerInterceptor类的intercept方法中可知,是先获取请求头,再获取请求体的。获取请求头的方法是httpStream类的readResponseHeaders()方法,这个方法的源码如下:

@Override public Response.Builder readResponseHeaders() throws IOException {
return readResponse();
}


继续看readResponse方法的源码:

public Response.Builder readResponse() throws IOException {
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
throw new IllegalStateException("state: " + state);
}

try {
while (true) {
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());

if (statusLine.code != HTTP_CONTINUE) {
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
}
}
} catch (EOFException e) {
// Provide more context if the server ends the stream before sending a response.
IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
exception.initCause(e);
throw exception;
}
}


从代码中发现,请求头数据通过StatusLine.parse(source.readUtf8LineStrict());解析到了statusLine 对象中。

下面看source的readUtf8LineStrict源码。source的实现类是RealBufferedSource。方法源码是:

@Override public String readUtf8LineStrict() throws IOException {
long newline = indexOf((byte) '\n');
if (newline == -1L) {
Buffer data = new Buffer();
buffer.copyTo(data, 0, Math.min(32, buffer.size()));
throw new EOFException("\\n not found: size=" + buffer.size()
+ " content=" + data.readByteString().hex() + "…");
}
return buffer.readUtf8Line(newline);
}


关键代码是indexOf方法,这个方法传递的参数是\n。方法的源码是:

@Override public long indexOf(byte b, long fromIndex) throws IOException {
if (closed) throw new IllegalStateException("closed");

while (true) {
long result = buffer.indexOf(b, fromIndex);
if (result != -1) return result;

long lastBufferSize = buffer.size;
if (source.read(buffer, Segment.SIZE) == -1) return -1L;

// Keep searching, picking up from where we left off.
fromIndex = Math.max(fromIndex, lastBufferSize);
}
}


我们看到,在此时source已经开始从服务器读取数据了。index传递的参数是\n,应该是以\n为分界点,\n之前的数据都读到响应头里面。\n之后的数据交给响应体来读取。

\n分界点是协议的约定,服务器也要遵从这个约定。将响应头数据放在\n之前,将响应体数据放在\n之后。

八,总结

OKHttp中向服务器提交数据有三个重要的类:RequestBody,httpStream,RealConnection。上传流程如下:

1,首先把参数保存在RequestBody对象中,RequestBody类又提供了写方法且接收一个输出流对象,但是不调用,在需要时再调用。

2,RealConnection负责Socket连接,并从连接中得到输出流对象。

3,在httpStream中调用RequestBody的写方法,并把RealConnection的输出流对象传递过去。

以上三步就完成了向服务器写数据的操作。

OKHttp中从服务器中获取数据有三个重要的类:ResponseBody,httpStream,RealConnection。上传流程如下:

1,RealConnection负责Socket连接,并从连接中得到输入流对象。

2,在httpStream中得到RealConnection的输入流对象,并创建ResponseBody对象,就输入流创建传递到ResponseBody对象中。

3,ResponseBody中提供了从输入流对象中得到数据的方法,此时就可以从ResponseBody中得到数据了。

以上三步就完成了从服务器读数据的操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息