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

  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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容