OKhttp源码学习(八)—— CallServerInterceptor

源码地址:https://github.com/square/okhttp

不知不觉已经来到了最后一个拦截器,前面做了各种处理,也建立了连接。接下来的CallServerInterceptor,应该就是对数据进行交换、读取以及构建结果的类了。

不过其实在这个拦截器之前,还可以自定义networkInterceptors。
在Okhttp的拦截器链条里面有两个地方可以自定义拦截:

  1. 最开始的时候(Interceptors):对发出去的请求做最初的处理,以及在拿到最后Reponse时候做最后的处理;

  2. 最后数据交换前(networkInterceptors):对发出去的请求做最后的处理,以及在拿到结果时候做最初的处理。

我们可以自定义拦截器,去处理我们需要做的事情。

下面言归正传:

intercept

和其他拦截器一样,最重要的方法就是这个intercept方法,拦截方法。

这个拦截方法主要步骤:

  1. 获取几个前面已经创建的重要类(httpCodec, streamAllocation, request);

  2. 先向sink(OutputStream)中写头信息(sink, 在创建连接时候已经创建好);

  3. 判断是否有请求体,如有,走4,5的操作,没有直接到6;

  4. 如果头部添加了"100-continue", 相对于一次见到的握手操作,只有拿到服务的结果再继续;

  5. 当“100-continue”成功或者不需要这个简单握手的,写入请求实体;

  6. finishRequest( 实际是调用了 sink.flush(), 来刷数据 )

  7. 读取头部信息(通过source(InputStream), 读取头部信息,状态码等)

  8. 构建Response, 写入原请求,握手情况,请求时间,得到的结果时间

  9. 通过Response 状态码判断以及是否webSocket判断,是否返回一个空的body, 或者读取Body信息(通过 source(InputStream) 读取)

  10. 读取到请求时的连接close ,或者服务器返回的 close, 进行断开操作;

  11. 对于204,205的特殊状态码进行处理。

有两个比较重要的类,就是sink以及source, 这个两个是Okio里面的一个重要概念,相当于我们熟悉的 OutputStream 以及 InputStream输入输出流。这里就不展开,有兴趣的可以了解一下 Okio这个 I/O 库。这两个类都是在创建连接的时候就已经建立,而在个拦截器里面就是对数据进行交换。

下面是源码阅读:

  @Override public Response intercept(Chain chain) throws IOException {

    // 1.获取几个前面拦截方法创建的,重要类
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();
    long sentRequestMillis = System.currentTimeMillis();

    //2. 先向sink(OutputStream)中写头信息
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;

   // 3.判断是否有请求实体的请求,用method判断
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
   
      //4. 如果头部添加了"100-continue", 相对于一次见到的握手操作,只有拿到服务的结果再继续
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      //5. 当前面的"100-continue",需要握手,但又握手失败,这个时候responseBuilder不是空的
      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        // 回调RequestBody的writeTo,写相应的数据
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }

    //6. 这里也是调用了一次 sink.flush()
    httpCodec.finishRequest();

    //7. 读取头部信息,状态码,信息等
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //8. 构建Response, 写入原请求,握手情况,请求时间,得到的结果时间
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    //9. 通过状态码判断以及是否webSocket判断,是否返回一个空的body
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      //读取Body信息
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    //10 .如果设置了连接 close ,断开连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    //11. HTTP 204(no content) 代表响应报文中包含若干首部和一个状态行,但是没有实体的主体内容。
    //HTTP 205(reset content) 表示响应执行成功,重置页面(Form表单),方便用户下次输入
    //这里做了同样的处理,就是抛出协议异常。
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

总结:

这个拦截器主要是通过连接好的通道进行数据的交换,有些细节没有展开,只对基本的流程进行了学习记录。留下两个可研究的点:

  1. HttpCodec是一个接口,其实有两个实现类,分别是HttpCodec1、HttpCodec2, 对应着http1 的读写数据和 http2的 读写数据。两者是有差异的;

  2. 还有前面提到的Okio,可以去了解一下。

系列:
OKhttp源码学习(一)—— 基本请求流程
OKhttp源码学习(二)—— OkHttpClient
OKhttp源码学习(三)—— Request, RealCall
OKhttp源码学习(四)—— RetryAndFollowUpInterceptor拦截器
OKhttp源码学习(五)—— BridgeInterceptor拦截器
OKhttp源码学习(六)—— CacheInterceptor拦截器
OKhttp源码学习(七)—— ConnectInterceptor拦截器
OKhttp源码学习(九)—— 任务管理(Dispatcher)

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

推荐阅读更多精彩内容