OKHttp原码分析(七)之HttpStream

一,概述

在上篇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中得到数据了。
以上三步就完成了从服务器读数据的操作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值