Okhttp源码分析专栏的几篇博客分析了Okhttp几个拦截器的主要功能,还剩下最后一个拦截器CallServerInterceptor没有分析,本篇博客就简单分析下该拦截器的功能。

在Okhttp拦截器链上CallServerInterceptor拦截器是最后一个拦截器,该拦截器前面的拦截器ConnectInterceptor主要负责打开TCP链接(详见《 OkHttp之ConnectInterceptor简单分析 》)。而CallServerInterceptor的主要功能就是—向服务器发送请求,并最终返回Response对象供客户端使用。

下面就从源码的角度来简单分析CallServerInterceptor的是怎么像服务器发送Request,以及生成Response对象的。

 public Response intercept(Chain chain) throws IOException {
    // 省略部分代码
    // 获取HttpCodec
    HttpCodec httpCodec = realChain.httpStream();
    // 省略部分代码
    Request request = realChain.request();
    //向服务器发送请求
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    // 检测是否有请求body
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {

      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        //构建responseBuilder对象
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

       //如果服务器允许发送请求body发送
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
         //省略部分代码
      }
    }

    //结束请求
    httpCodec.finishRequest();

    //构建请求buidder对象
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      //省略部分代码
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    //省略部分代码

    return response;
  }

该方法首先是获取了httpCodec对象,该对象的主要功能就是对不同http协议(http1.1和http/2)的请求和响应做处理,该对象的初始化是在ConnectIntercepor的intercept里面:

 HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);

最终httpCodec的初始化又是在StreamAllocation的newStream方法(详见《 OkHttp之ConnectInterceptor简单分析 》):

  public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
     //:省略部分代码;

      HttpCodec resultCodec = resultConnection.newCodec(client, this);

  }
    public HttpCodec newCodec(
      OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, streamAllocation, http2Connection);
    } else {
      //设置socket的读超时时间
      socket.setSoTimeout(client.readTimeoutMillis());
      //InputStream的超时时间
      source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
      //OutputStream的超时时间
      sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

可以发现Okhttp的提供了两种HttpCodec的实现类,如果使用了http2协议则返回Http2Codec,否则返回Http1Codec!并且设置了超时时间,本篇就以Http1Codec对象来进行分析。

我们知道Http发送网络请求前两个步骤是:

1、建立TCP链接

2、客户端向web服务器发送请求命令:形如GET /login/login.jsp?username=android&password=123 HTTP/1.1的信息

在Okhttp中ConnectInterceptor负责第一个步骤,那么第二个步骤是如何实现的呢?答案就是httpCodec对象的writeRequestHeaders方法。(该方法在CallserverInterceptor的intercept里面调用,见上面代码)

 public void writeRequestHeaders(Request request) throws IOException {
  //RequestLine.get用来构建形如GET xx HTTP/1.1的字符串
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
  //像服务器发送请求,形如GET xxx HTTP/1.1
    writeRequest(request.headers(), requestLine);
  }

可以发现Okhttp通过OkIO的Sink对象(该对象可以看做Socket的OutputStream对象)的writeRequest来向服务器发送请求的。

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;
  }

我们知道HTTP支持post,delete,get,put等方法,而post,put等方法是需要请求体的(在Okhttp中用RequestBody来表示)。所以接着writeRequestHeaders之后Okhttp对请求体也做了响应的处理:

    //如果当前request请求需要请求体
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {

      //询问Server使用愿意接受数据
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        //构建responseBuilder对象
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

       //向服务器发送请求体
      if (responseBuilder == null) {

         //发送请求体,详见下文描述
      } else if (!connection.isMultiplexed()) {
        //省略部分代码
      }
    }

通过上面的代码可以发现Okhttp对Expect头部也做了支持,上面代码对客户端是否使用该头部做了判断,“100 continue”的作用就是:客户端有一个RequestBody(比如post或者PUT方法)要发给服务器,但是客户端希望在发送RequestBody之前查看服务器是否接受这个body,服务端在接受到这个请求后必须进行响应。客户端通过Expect首部来发送这个消息,当然如果客户端没有实体发送,就不应该发送100 continue 首部,因为这样会使服务器误以为客户端有body要发送。所以okhttp在发送这个之前要permitsRequestBody来判断。当然常规的get请求是不会走这个方法的。

如果服务器允许发送ReqeustBody,那么就通过下面这三行代码来发送请求体:

  //构建请求体对象组成的输入流
  Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        //发送请求体
        request.body().writeTo(bufferedRequestBody);

最终调用ReqeustBody的writeTo方法来发送请求体,实际上是调用bufferedRequestBody对象的write方法,简单实例如下(当然实际可能是FormBody或者是自定义的ReqeustBody):

ublic static RequestBody create(
      final @Nullable MediaType contentType, final ByteString content) {
    return new RequestBody() {
      //省略部分代码
      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.write(content);
      }
    };
  }

到现在为止,客户端向服务端发送请求的部分已经讲解完毕,下面就剩下读取服务器响应然后构建Response对象了:

//构建请求buider对象
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //构建response对象
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      //返回空的即无效的响应
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

上面的代码做了三个工作:

1、调用HttpCodec的readResponseHeaders方法读取服务器响应的数据,构建Response.Builder对象(以Hppt1Codec分析):

 public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
      //省略部分代码
      //读取服务器
      StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)//http协议版本
          .code(statusLine.code)//http响应状态码
          //http的message :like "OK" or "Not Modified"
          .message(statusLine.message)
          .headers(readHeaders());//http响应header
      //省略部分代码

      return responseBuilder;

  }

2、通过ResopnseBuilder对象来最终创建Response对象,并返回。

最关键的是服务器的响应体或者响应内容是如果传给Response的,代码如下:

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

Response的body通过httpCodec对象的openResponseBody传进来,进入Http1Codec对象的openResponseBody方法看看都做了些神马:

public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }

很简单,openResponseBody将Socket的输入流InputStream对象交给OkIo的Source对象(在本篇博文中只需简单的将Sink作为Socket的输入流,Source作为Socket的输入流看待即可,详细的分析可参考OKIO),然后封装成RealResponseBody(该类是ResponseBody的子类)作为Response的body.

那么我们怎么通过这个body来获取服务器发送过来的字符串呢?ResponseBody提供了string()方法:

  public final String string() throws IOException {
    BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      //InputStream 读取数据
      return source.readString(charset);
    } finally {
      Util.closeQuietly(source);
    }
  }

string()方法也很简单,就是通过一些处理然后让调用source.readString来读取服务器的数据。需要注意的是该方法最后调用closeQuietly来关闭了当前请求的InputStream输入流,所以string()方法只能调用一次,再次调用的话会报错,毕竟输入流已经关闭了,你还怎么读取数据呢?

到此为止CallServerInterceptor简单分析完毕,总结下主要做了如下工作:

1、获取HttpCodec对象,对<=Http1.1之前的或者http/2不同协议的http请求处理。

2、发送http请求数据,构建Resposne.Builder对象,然后构建Response并返回。

到此为止Okhttp从发起请求到响应请求生成Response对象的流程已经分析完毕。如果想详细了解Okhttp工作原理的话,可参考博主的Okhttp源码分析专栏,如有不当之处,欢迎批评指正。

Okhttp之CallServerInterceptor简单分析的更多相关文章

  1. OkHttp之ConnectInterceptor简单分析

    在< Okhttp之CacheInterceptor简单分析 >这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览 ...

  2. Okhttp之CacheInterceptor简单分析

    <OkHttp之BridgeInterceptor简单分析 >简单分析了BridgeInterceptor的工作原理,在Okhttp的拦截器链上BridgeInterceptor的下一个拦 ...

  3. OkHttp之BridgeInterceptor简单分析

    在< Okhttp源码简单解析(一) >这篇博客简单分析了Okhttp请求的执行流程,通过该篇博客我们知道OkHttp的核心网络请求中内置"拦截器"发挥了重大作用:本篇 ...

  4. Okhttp之连接池ConnectionPool简单分析(一)

    开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解: <Okht ...

  5. Okhttp对http2的支持简单分析

    在< Okhttp之RealConnection建立链接简单分析>一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开t ...

  6. Okhttp之RealConnection建立链接简单分析

    在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...

  7. Okhttp之RouteSelector简单解析

    继前面的几篇OKhttp的拦截器简单分析之后,对于后续Okhttp之间的分析自己也着实琢磨了一段时间,是分析RealConnection?还是ConnectionPool,随着对Okhttp源码的深入 ...

  8. 简单分析JavaScript中的面向对象

    初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...

  9. CSipSimple 简单分析

    简介 CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话.连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果 ...

随机推荐

  1. 微信网页跳转页面常见bug处理

    微信网页跳转页面常见bug处理 1.不要直接用a链接直接跳转 2.url后加上时间戳 function gohome() { window.location.href = "../home/ ...

  2. HashMap中的hash函数

    在写一个HashSet时候有个需求,是判断HashSet中是否已经存在对象,存在则取出,不存在则add添加.HashSet也是通过HashMap实现,只用了HashMap的key,value都存储一个 ...

  3. c++ primer plus 第五章 课后题答案

    #include <iostream> using namespace std; int main() { ; cout << "Please enter two n ...

  4. js 转码思维

    <body> <script type="text/javascript"> // var el = document.createElement('scr ...

  5. tp5.0 composer命令插件

    1.单元测试composer require topthink/think-testing 1.* (5.0) composer require topthink/think-testing 5.1官 ...

  6. Codeforces 827C - DNA Evolution

    827C - DNA Evolution 思路: 写4*10*10个树状数组,一个维度是4(ATCG),另一个维度是长度len,另一个维度是pos%len,因为两个pos,如果len和pos%len相 ...

  7. python下编译py成pyc和pyo和pyd

    https://www.cnblogs.com/dkblog/archive/2009/04/16/1980757.html

  8. C#,ArcGIS Engine开发入门教程

    C#,ArcGIS Engine开发入门教程 转自:http://blog.csdn.net/yanleigis/article/details/2233674  目录(?)[+] 五实现 一 加载A ...

  9. Python在七牛云平台的应用(二)图片瘦身

    (一)七牛云平台的图片瘦身功能简介:(引用自官网) 针对jpeg.png格式图片 瘦身后分辨率不变,格式不变. 肉眼画质不变. 图片体积大幅减少,节省 CDN 流量 官网给的图片压缩率很高,官网给的「 ...

  10. ORACLE常见方法使用(转)

    1.DBMS_LOB包的使用 2.如何释放DBMS_LOB.CREATETEMPORARY的空间 3.oracle数组