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中得到数据了。
以上三步就完成了从服务器读数据的操作。
相关文章推荐
- (4.2.36.2)HTTP之OkHttp(二): okhttp3与旧版本okhttp的区别分析
- OKHttp原码分析(一)
- Android 4.4以上使用HttpURLConnection底层使用OkHttp实现的源码分析
- OKHttp原码分析(四)之getResponseWithInterceptorChain方法
- Okhttp使用和源码分析二(OkHttp3.x用法)
- Okhttp使用和源码分析一(OkHttp2.x用法)
- OKHttp网络框架源码解析(一)okHttp框架同步异步请求流程和源码分析
- OkHttp的使用分析,okhttp的多种getpost方式
- OKHttp原码分析(八)之必须明白的几个问题
- Okhttp对http2的支持简单分析
- OKHttp原码分析(六)之RealConnection
- OkHttp3 HTTP请求执行流程分析
- OKHttp源码分析3 - HttpEngine底层实现
- OKHttp原码分析(五)之Interceptor
- Okhttp使用和源码分析三(OkHttp源码分析)
- BT源代码学习心得(七):跟踪服务器(Tracker)的代码分析(HTTP协议处理对象)
- 处理IHttpHandler和IHttpModule接口来做流量分析系统
- acegi流程分析之一《Acegi 中的HttpSessionEvent 监听机制 窥视Acegi的 工作流程》
- HTTP Streaming protocol 分析笔记
- XMLHTTP发送HTTP请求失败的可能性分析(二)