okhttp之CallServerInterceptor 分析

CallServerInterceptor是okhttp中的最后一个拦截器,用来向服务器发送客户端的请求数据,并且封装服务器返回来的Response。开始分析代码:

@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//获取到HttpCodec
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();

long sentRequestMillis = System.currentTimeMillis();

realChain.eventListener().requestHeadersStart(realChain.call());
//向服务器中写入请求头
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);

Response.Builder responseBuilder = null;
//如果请求中有请求体
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.
  //如果请求头中包含Expect:100-continue的请求头
  if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
    httpCodec.flushRequest();
    realChain.eventListener().responseHeadersStart(realChain.call());
    //读取服务端返回的响应
    //readResponseHeaders(true)这个方法里面做的操作如下:
    //1、读取服务器返回的响应头
    //2、根据响应码和传入的参数判断返回的respone.builer是否为空
    // (当传入的参数为true(这个可以理解为客户端希望继续发送请求体)并且
    // 服务端返回的状态码也是希望我们继续传入请求体的时候,就返回null)
    responseBuilder = httpCodec.readResponseHeaders(true);
  }
//如果responseBuilder为空,说明可以继续传入请求体
  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();
    realChain.eventListener()
        .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
//    这里的else if表示不需要传入请求体,那么这次请求就算是结束了,
//    如果不是http2协议的话,这次连接就可以关闭掉了
  } else if (!connection.isMultiplexed()) {

    // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
    // from being reused. Otherwise we're still obligated to transmit the request body to
    // leave the connection in a consistent state.
    streamAllocation.noNewStreams();
  }
}

//执行到这里,不管有没有发送请求体,本次请求就算是结束了
httpCodec.finishRequest();

由于在之前的拦截器中已经和服务器的连接打通了,在这个拦截器中只负责数据的传输。上面的代码就是向服务端发送请求的全部了,可以看到发送请求一共做了如下几步:

1、获取到httpCodec,这是一个接口,主要是封装了request的写入和response的读取,可以理解为通过这个接口向外发送请求和接收数据。这个实体类是在之前的拦截器中生成的,我们直接拿过来用便好。其实现类为Http1Codec和Http2Codec,分别对应了HTTP/1.1和HTTP/2

2、向服务端发送请求头

 //向服务器中写入请求头
httpCodec.writeRequestHeaders(request);

这里使用Http1Codec中的代码,其中用到的sinkOkIO的Sink对象(该对象可以看做Socket的OutputStream对象)

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

3、判断是否需要向服务器发送请求体,主要通过以下几方面判断:

1)HttpMethod.permitsRequestBody(request.method()) && request.body() != null

通过判断请求方式(比如get请求是不需要请求体的,那么就不需要发送请求体)和请求体是否为空来判断。

2)在符合上面条件的情况下,再次判断请求头中是否有Expect:100-continue,如果有,那么就获取获取到请求头的响应状态码,判断服务端是否想要接收请求体。如果服务端返回的状态码是100,表示接收请求体,那么就通过readResponseHeaders()方法返回的响应体responseBuilder为空。为什么返回空呢?在后面的代码中是通过responseBuilder是否为空来判断是否需要发送请求体的。

    //1、读取服务器返回的响应头
    //2、根据响应码和传入的参数判断返回的respone.builer是否为空
    // (当传入的参数为true(这个可以理解为客户端希望继续发送请求体)并且
    // 服务端返回的状态码也是希望我们继续传入请求体的时候,就返回null)
    responseBuilder = httpCodec.readResponseHeaders(true);

Http1Codec中的代码

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
  。。。。。
    try {
      StatusLine statusLine = StatusLine.parse(readHeaderLine());
        //这里生成了responseBuilder
      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());
    //但是如果code为100并且expectContinue为true(从上面传进来的true)时,返回null
    //需要请求体,暂时不返回响应体
      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      } else if (statusLine.code == HTTP_CONTINUE) {
        state = STATE_READ_RESPONSE_HEADERS;
        return responseBuilder;
      }

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

4、发送请求体

从3中判断如果返回的responseBuilder为空,那么就发送请求体

//如果responseBuilder为空,说明可以继续传入请求体
  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();
    realChain.eventListener()
        .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
    //  这里的else if表示不需要传入请求体,那么这次请求就算是结束了,
    //  如果不是http2协议的话,这次连接就可以关闭掉了
  } else if (!connection.isMultiplexed()) {

    // If the "Expect: 100-continue" expectation wasn't met, prevent the    HTTP/1 connection
    // from being reused. Otherwise we're still obligated to transmit the request body to
    // leave the connection in a consistent state.
    streamAllocation.noNewStreams();
  }
}

//    执行到这里,不管有没有发送请求体,本次请求就算是结束了
httpCodec.finishRequest();

代码中的注释应该已经比较清楚了,主要是通过BufferedSink把body写入输出流传送给服务器。

写到这里,发送请求就算完成了,下面来看获取响应体。

if (responseBuilder == null) {
  realChain.eventListener().responseHeadersStart(realChain.call());
  //有请求体的情况下获取responseBuilder
  responseBuilder = httpCodec.readResponseHeaders(false);
}
//封装respone响应 写入原请求,握手情况,请求时间,得到的结果时间
Response response = responseBuilder
    .request(request)
    .handshake(streamAllocation.connection().handshake())
    .sentRequestAtMillis(sentRequestMillis)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build();

上面的代码主要是通过httpCodec.readResponseHeaders(false)获取到response,这时的response并不包含请求体。然后再根据响应码来做不同的处理

int code = response.code();
if (code == 100) {
  //如果响应码是100,重新读取一遍响应头
  // server sent a 100-continue even though we did not request one.
  // try again to read the actual response
  responseBuilder = httpCodec.readResponseHeaders(false);

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

  code = response.code();
}
    //code=101  Switching Protocols 服务器将遵从客户的请求转换到另外一种协议
 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 {
    //这里才为response附上了服务器返回的body
  response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();
}

//根据connection值关闭连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
    || "close".equalsIgnoreCase(response.header("Connection"))) {
  streamAllocation.noNewStreams();
}
    
//• 204 - No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页/面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。 
//• 205 - Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
  throw new ProtocolException(
      "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}

关于获取响应体到这里就结束了,一共分两步:

1)组装到响应体的头部

2)根据头部的响应码,来判断有没有响应体,并组装响应体

到这里整个CallServerInterceptor拦截器就介绍完了,主要做了这么几件事:

1、发送请求头

2、根据请求方式和响应头来判断是否发送请求体

3、如果需要发送请求体,通过BufferedSink写入请求体

4、封装响应头

5、根据响应码组装响应体

    原文作者:黑猫警长_01
    原文地址: https://www.jianshu.com/p/d0d19993cb9f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞