美文网首页
OKHttp源码分析----责任链的最后一环

OKHttp源码分析----责任链的最后一环

作者: 张三疯啊啊啊 | 来源:发表于2018-08-03 15:26 被阅读0次
  Response getResponseWithInterceptorChain() throws IOException {
      // Build a full stack of interceptors.
     List<Interceptor> interceptors = new ArrayList<>();
     interceptors.addAll(client.interceptors());
     interceptors.add(retryAndFollowUpInterceptor);
     interceptors.add(new BridgeInterceptor(client.cookieJar()));
     interceptors.add(new CacheInterceptor(client.internalCache()));
     interceptors.add(new ConnectInterceptor(client));
     if (!forWebSocket) {
       interceptors.addAll(client.networkInterceptors());
     }
     interceptors.add(new CallServerInterceptor(forWebSocket));

     Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
     originalRequest, this, eventListener, client.connectTimeoutMillis(),
     client.readTimeoutMillis(), client.writeTimeoutMillis());

     return chain.proceed(originalRequest);
 }

OKHttp的责任链可以说是非常经典的设计模式了。这里说一下责任链的最后一环。

通过源码分析,发现最后一环是在 CallServerInterceptor

  /** This is the last interceptor in the chain. It makes a network call to the server. */
 public final class CallServerInterceptor implements Interceptor {
 }

具体的请求还是要看intercept方法。

@Override public Response intercept(Chain chain) throws IOException {
                  RealInterceptorChain realChain = (RealInterceptorChain) chain;
    .......
    //写入请求头
    realChain.eventListener().requestHeadersStart(realChain.call());
    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      ..........
      //写入请求体
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        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();
        .........
    }

    httpCodec.finishRequest();
    //读取响应头
    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
    //读取响应体
    response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();
   .......
    return response;
  }

可以看出关键的实现步骤,基本都封装在了httpCodec这个类中。

 @Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }

  /** Returns bytes of a request header for sending on an HTTP transport. */
  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;
  }

可以看出,通过for循环不断读取header的内容,写入到sink中。
sink是OKio的类,也是square公司推出的框架。
继续深入这个sink:

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

发现sink是在构建http1Codec的时候就传入进来了。继续找sink的初始化位置,经过一番寻找,发现是在
RealConnection这个类中,同时初始化了Sink和Source

        /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
      private void connectSocket(int connectTimeout, int readTimeout, Call call,
          EventListener eventListener) throws IOException {
        Proxy proxy = route.proxy();
        Address address = route.address();

        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()
            : new Socket(proxy);
        eventListener.connectStart(call, route.socketAddress(), proxy);
        rawSocket.setSoTimeout(readTimeout);
        try {
          Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
        } catch (ConnectException e) {
          ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
          ce.initCause(e);
          throw ce;
        }
        try {
          source = Okio.buffer(Okio.source(rawSocket));
          sink = Okio.buffer(Okio.sink(rawSocket));
        } catch (NullPointerException npe) {
          if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
            throw new IOException(npe);
          }
        }
      }

我们知道http请求的底层实现是socket,这里就是socket创建和连接的地方,而我们所用到的sink和source也都是在这里进行的初始化。

      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));

有没有很熟悉的感觉,我们在学java基础的时候,模拟socket连接,也是获取一个输入流,输出流。
客户端socket:
通过outputStream向socket写入数据。--------> 等同于这里的sink
通过inputStream读取socket的数据。---------> 等同于这里的source。

当然,关于为什么能写入数据到socket,从socket读取数据,这里就是更底层的东西了,能力有限,暂时不做分析。

继续回到刚才的代码

    long contentLength = request.body().contentLength();
    CountingSink requestBodyOut =
        new CountingSink(httpCodec.createRequestBody(request, contentLength));
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();

request.body() 有两种实现方式,就是我们经常用到的formbody 和 MultipartBody.
这里看writeto()方法。

      private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
          long byteCount = 0L;

          Buffer buffer;
          if (countBytes) {
            buffer = new Buffer();
          } else {
            buffer = sink.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循环,可以猜测一下就知道,这里就是写入请求体的方法。
看了一下encodeNames的实现:

      FormBody(List<String> encodedNames, List<String> encodedValues) {
        this.encodedNames = Util.immutableList(encodedNames);
        this.encodedValues = Util.immutableList(encodedValues);
      }

可以看到就是key和value的集合。
这里实现了把请求体写入到了sink中(sink的buffer中)。

那么什么时候,是从buffer写入到sink中呢?

      bufferedRequestBody.close();

close()的时候,把buffer的数据写入到sink中,也就是写入到socket连接中。

写数据的逻辑理清之后,从socket读数据的逻辑就特别清晰了,直接看关键代码:

      if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(false);
      }

继续看httpCodec.readResponseHeaders(false) 这句代码:

      @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
        .......
        StatusLine statusLine = StatusLine.parse(readHeaderLine());
        .......
      }

看一下statusline的成员变量:

      public final Protocol protocol;
      public final int code;
      public final String message;

封装了响应码等信息,具体的实现还是要到readHeaderline()方法中。

      private String readHeaderLine() throws IOException {
          String line = source.readUtf8LineStrict(headerLimit);
          headerLimit -= line.length();
          return line;
      }

最后找到了source.read()方法,也就是从socket中读取数据。
最后一步解析响应体数据,还是要回到CallServerIntercept的intercept()方法中:

      response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();

看了一下httpCodec.openResponseBody(response)里面的代码:

      if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
        Source source = newChunkedSource(response.request().url());
        return new RealResponseBody(contentType, -1L, Okio.buffer(source));
      }

只是初始化了一个chunkSource,可以看出应该是分块获取数据的。但是并没有涉及读数据的方法。

之前我看到这一直找不到在哪里读取响应体,后来忽然想到,只是封装bufferSource到responseBody里面,只有真正用的时候才会从source的buffer中读取出来。
我们一般调取响应的时候,会用response.body.string(),继续看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()回调的就是之前的 newChunkSource.
先通过Util.bomAwareCharset(source, charset()) 获取编码格式,然后调用source.readString("UTF_8例")的格式读取字符串。
里面又回调到了RealBufferSource.readString()方法,

      @Override public String readString(Charset charset) throws IOException {
      if (charset == null) throw new IllegalArgumentException("charset == null");

      buffer.writeAll(source);
      return buffer.readString(charset);
      }

先看buffer.writeAll(source):

      @Override public long writeAll(Source source) throws IOException {
      if (source == null) throw new IllegalArgumentException("source == null");
      long totalBytesRead = 0;
      for (long readCount; (readCount = source.read(this(Sink), Segment.SIZE)) != -1; ) {
        totalBytesRead += readCount;
      }
      return totalBytesRead;
      }

这里是在Buffer里面实现的,又是熟悉的for循环。

     source.read(sink) == sink.write(source)

sink.write()是一种高效的读写交互方式,底层是通过链表重指向,而不是数据拷贝实现的

      @Override public void write(Buffer source, long byteCount) {
      // Move bytes from the head of the source buffer to the tail of this buffer
      }

然后继续回到RealBufferSource.readString()源码,buffer.readString(charset)

      @Override public String readString(long byteCount, Charset charset) throws EOFException {
      ............
      Segment s = head;
      ............
      String result = new String(s.data, s.pos, (int) byteCount, charset);
      ............

    }

如果只是文件的读写,不涉及socket交互就可以分析具体实现了,但是能力有限,再深入应该就到socket底层了,猜想实现原理是:
用户-----》sink写入请求-------》socket发送请求,将返回数据复制给buffer中的head-------》获取source------》读取head。

相关文章

网友评论

      本文标题:OKHttp源码分析----责任链的最后一环

      本文链接:https://www.haomeiwen.com/subject/nqqyvftx.html