OkHttpClient 源码分析 1(基于3.9.0的源码)

news/2024/5/19 0:20:09 标签: 运维, 移动开发, java

OkHttpClient是目前开发 android 应用使用最广泛的网络框架,最近看了阿里的 httpdns 里面对于 dns 的处理,我们团队就想调研一下在项目中有什么利弊,并且框架中是否对 socket 的连接是怎么缓存的。下面就带着问题去分析一下这个框架:

new OkHttpClient.Builder().build();这个建造者的每个传参在源码分析2中有详细的讲解

发送请求的只有这一个方法,就顺着点进去看下是怎么设计的

java copyable">  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
复制代码
java copyable">/**
  *注意这个方法是没有声明 public 的,只能在包内部调用,所以用户无法直接调用.
  *并在这里eventListenerFactory为每个 call 设置了监听
  **/
 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }
  //私有构造方法,只能通过上面的静态方法调用,这样的好处是,防止框架使用者传参有误导致无法正常使用
    private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }
复制代码

使用者只需要传入一个 Request 就可以使用了

java copyable">//request 也是通过构造者生成的, 整个请求的头,行,体都从这里传入,并多了一个 tag 作为标识
  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;//使用者可以设置一个唯一标识,拿到 Request 的时候分不出是哪个请求
    //默认设定请求使用了 GET 方法,并传入空 Headers
    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
}
复制代码

HttpUrl url, String method

通过方法url()传入,使用HttpUrl对 url 封装,方便获取 url 的各个部分信息。可以直接调用get(),head(), post(body),delete(),put(body)等设置 methord 并传入 Body,也可以使用method(String method,RequestBody body)直接输字符串,这个方法里进行了校验,传参出错抛出异常

Headers

Headers.Builder中使用 addLenient,add 添加一行 header,可以是“key:value”这样的字符串,也可以是两个参数 key,value。 调用 add 检测中文或不可见字符会抛出异常,而addLenient 不会检测。 还有一个 set 会覆盖已经存在所有name一样的值

Headers 中用一个数组保存数据,偶数位是 key,奇数位是 value,里面的大部分方法是处理这个数组的。 除了用 buidler 生成header,还可以调用静态方法of传入一个 map 或者[key1,value1,key2,value2...]生成

java copyable"> String get(String name) //获取 key=name 的 value
 public List<String> values(String name) //和 get 方法类似,如果有多个 key=name 的条目返回所有的 value。(http 协议的header不限制顺序,不排重)
 public Date getDate(String name)//内部调用get(name)并转成时间
 public int size() //header 的条数,保存了key、value数组的一半
 public String name(int index) //获取指定位置的 name,index相对于 header 的条数,不是数组的 index
 public String value(int index)//获取指定位置的value,index相对于 header 的条数,不是数组的 index
 public Set<String> names()//获取所有的 name,由于是 set 重复的只算一条
 public long byteCount() //整个 header 要占用的长度,包含分割的“: ”和回车换行
 public Map<String, List<String>> toMultimap()//把这个 Headers 转成 map,由于可能有多个相同的 key,value 要用 list返回
复制代码

RequestBody

框架中两个实现类:FormBody上传普通的参数请求,MultipartBody上传文件。RequestBody中三个静态 create 方法传入MediaType和ByteString、byte[]、File 满足了大部分请求需要,并且传给MultipartBody。

如果需要自定义需要至少实现contentType,writeTo,分别传入数据类型和写入。如果有断点续传的要求复写contentLength。

java copyable"> public abstract MediaType contentType();
  public long contentLength() throws IOException {
    return -1;
  }
  public abstract void writeTo(BufferedSink sink) throws IOException;
复制代码
java copyable">/**出来下面三个主要方法的调用还有一些获取 name 和 value 的方法,和 builder 里添加name 和 value的方法,容易理解不做解释。
  **/
public final class FormBody extends RequestBody {
  private static final MediaType CONTENT_TYPE = MediaType.parse("application/x-www-form-urlencoded");

  @Override public MediaType contentType() {
    return CONTENT_TYPE;//返回了x-www-form-urlencoded的类型,适用于带参数的接口请求
  }

  @Override public long contentLength() {
    return writeOrCountBytes(null, true);//调用了writeOrCountBytes
  }

  @Override public void writeTo(BufferedSink sink) throws IOException {
    writeOrCountBytes(sink, false);//调用了writeOrCountBytes
  }

  private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
    long byteCount = 0L;
    Buffer buffer;
    if (countBytes) {//只需要获取 body 的长度,contentLength
      buffer = new Buffer();
    } else {
      buffer = sink.buffer();//写入数据 writeTo
    }
    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) {//如果是 contentLength 调用需要返回长度
      byteCount = buffer.size();
      buffer.clear();
    }
    return byteCount;
  }
复制代码

MultipartBody和FormBody计算 body 长度和写入数据的方法类似,但是MediaType类型比较多,看服务器需要哪种类型的,我们公司服务器是MediaType.parse("multipart/form-data")的,在 builder 中添加了多个part,writeTo的时候用boundary分割开。

java copyable">public static final class Builder {
    private final ByteString boundary;//每个 part 的分割线,基本上都是一个够长的随机串
    private MediaType type = MIXED;//默认类型MediaType.parse("multipart/mixed")
    private final List<Part> parts = new ArrayList<>();//保存了所有的 part

    public Builder(); //随机生成了一个boundary,没有特殊需求不要修改
    public Builder(String boundary); //自定义一个boundary
    public Builder setType(MediaType type);//自定义类型
    public Builder addPart(Headers headers, RequestBody body); //添加一个 part 如果上传文件可以自定义 headers, FormBody.create(MultipartBody.FORM, file)
    public Builder addFormDataPart(String name, String value); //添加一个字符串的 body
    /**添加普通 header 的文件.
      *@params name 是服务器接受 file 的字段名;
      *@params filename 本地的文件名
      *@params body 传FormBody.create(MultipartBody.FORM, new File(filePath))
      **/
    public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) ;
复制代码

同步请求 execute() 异步请求 enqueue()

使用字段 executed 防止一个 RealCall 调用 enqueue 或者 execute 多次,调用 eventListener.callStart 开始请求,两个方法都调用到了client.dispatcher()(okHttpClient.Builder中设置见 源码解析2) 里,其中enqueue使用了NamedRunnable, 最终都调用到了getResponseWithInterceptorChain();发送请求的核心方法。

java copyable">public abstract class NamedRunnable implements Runnable {
  protected final String name;
//在子类AsyncCall的传参是“OkHttp%s”,redactedUrl()这个方法最终生成了一个和请求url相似的string。
  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);//拼接成一个字符串,标识okhttp的请求
  }
/**在执行请求时,替换了线程名,子线程的名字可能是dispatcher或者用户定义的线程池设置的。调试更方便
  *@methord execute 倒了个方法名,就是run方法。okhttp框架的方法名起得都很怪异,java原生 execute 是在origin thread调用
  **/
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  protected abstract void execute();
}
复制代码

getResponseWithInterceptorChain

设计思路是使用RealInterceptorChain保存了所有参数,相比旧版使用ApplicationInterceptorChain调用HttpEngine完成请求,新版依次调用拦截器interceptors的list生成请求,并把框架使用者设置的拦截器插入进来,可以在请求过程中拿到并修改包含request和response的所有值,提高了扩展性,并且这种链式依次调用应该会更容易理解。

client.interceptors()、client.networkInterceptors() (源码分析2) 在OkHttpClient.Builder里传入,分别在 建立连接前 和 request 写入服务器前 注入。 networkInterceptors 这个名字很到位,表明了是在网络连接之后的注入的拦截器,最终写入数据到socket中并获取服务器返回的流,形成了一条数据调用链。

java copyable">Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//OkHttpClient.Builder.addInterceptor(Interceptor interceptor)
    interceptors.add(retryAndFollowUpInterceptor);//错误处理,和其他的拦截器相比 这个拦截器先初始化了,这样设计我觉得是处理 cancel 用的
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//拼接http协议 组装header
    interceptors.add(new CacheInterceptor(client.internalCache()));//缓存处理
    interceptors.add(new ConnectInterceptor(client));//使用连接池建立连接
    if (!forWebSocket) {//forWebSocket 在 httpclient.newCall时是 false
      interceptors.addAll(client.networkInterceptors());//添加OkHttpClient.Builder.addNetworkInterceptor(Interceptor interceptor)
    }
    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);//开始请求
  }
复制代码

Interceptor.Chain 它只有一个子类 RealInterceptorChain(旧版有两个子类) 实际上就是一个保存了所有参数的类,里面只有一个有用的方法 Response proceed(Request request)。像是 Interceptor 对应的 bean,并且每一个拦截器都有一个 chain。

java copyable">/**链式调用下一个拦截器,直到CallServerInterceptor发出请求,不在调用proceed。
  *@params index 标识调用到第几个拦截器了,当然不会超出interceptors.size(),逻辑上看是不会抛出AssetionError的
  *@params calls 标识和拦截器对应的Chain调用了几次,如果用户定义的interceptor调用了多次 chain.proceed会抛出异常
  **/
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {//connect不支持这个请求
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)  + " must retain the same host and port");
    }
    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {//如果用户定义的interceptor调用了多次 chain.proceed会抛出异常
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)  + " must call proceed() exactly once");
    }
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);//arraylist的下一个拦截器
    Response response = interceptor.intercept(next);//这里的 interceptor如果命名为next更好理解一点
    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {//请求已经完成了,确保proceed必须仅仅调用一次,如果没有调用 index+1小于size,如果调用多次,calls会大于1
      throw new IllegalStateException("network interceptor " + interceptor  + " must call proceed() exactly once");
    }
    // Confirm that the intercepted response isn't null.
    if (response == null) {//已经走完了所有的拦截器,这时候必须有response
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }
    if (response.body() == null) {//response必须有相应体。分开判断。提示不同的错误日志,更好调试
      throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body");
    }
    return response;
  }
}
复制代码

RetryAndFollowUpInterceptor

如果用户没有设置拦截器,第一个进入了RetryAndFollowUpInterceptor,通过 followRedirects、followSslRedirects(源码分析2)控制302,401等响应码逻辑。

java copyable">/**使用StreamAllocation找到一个可用的 connect,并且传给后面的拦截器继续处理,
  *处理完成生成了 response,通过响应码判断是否还需要后续请求,
  *如果需要后续请求,判断StreamAllocation这个连接是否可以复用或者 new 一个新的。
  *@methord createAddress 可能是因为变量太多,不容易通过方法传递弄个Address类,没有任何逻辑
  *@field canceled 如果取消请求了,这里抛出了IOException异常
  *@constant MAX_FOLLOW_UPS最多可以重定向20
  **/
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//3.9版本只有这一中拦截器,如果用户定义自己的拦截器要继承RealInterceptorChain了
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //处理流和连接池的类
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace);

    int followUpCount = 0;//重定向多少次了
    Response priorResponse = null;//如果有重定向,记录上一次返回值
    while (true) {//如果不 return 或抛出异常 会一直重试下去
      if (canceled) {//调用了 Call.cancel,如果这个 call 在子线程的队列里还没有发出去就可以 cancel 掉
        streamAllocation.release();
        throw new IOException("Canceled");
      }
      Response response;//记录请求结果
      boolean releaseConnection = true;//执行后面的拦截器的抛出的异常没有抓到,是连接不到服务器或者服务器出错,要释放 StreamAllocation
      try {//执行后续的拦截器(下面一一介绍),当所有的拦截器执行完,就有了 response
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        /* recover() 这个方法判断如果连接出现问题是否可以重试
         *先判断 Okhttpclient.retryOnConnectionFailure(),
         *在判断是否可以通过重试解决的Exception,
         *如果有更多的连接方案返回 true */
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();//路由认证失败抛出异常
        }
        releaseConnection = false;
        continue;//掉用了streamAllocation.hasMoreRoutes,去找下一个可用的 connection
      } catch (IOException e) {
        // 和RouteException类似,重试请求还是抛出异常
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        if (releaseConnection) {//如果有请求过程中抛出了异常,connection 不可用了,要释放掉
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }
      if (priorResponse != null) {//如果重试了一次或多次,要把第一个响应的除了 body 部分返回给调用者
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder().body(null).build())
            .build();
      }
      Request followUp = followUpRequest(response);
      if (followUp == null) {//根据响应码判断是否需要后续请求,不需要后续处理则返回 response
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());//body 不需要复用给priorResponse,则关掉

      if (++followUpCount > MAX_FOLLOW_UPS) {//如果重新发起请求次数超过20次就不在重试了
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {//返回来的 requestbody 继承了 UnrepeatableRequestBody。
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
//通过方法sameConnection判断是不是要请求同一个 url,如果不是统一要 new 一个新的StreamAllocation
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),   createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {//当前的streamAllocation没有设置协议类型,正常不会进入到这里
        throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;//继续请求这个
      priorResponse = response;//保存这次请求的结果,需要里面的 request 和 header
    }
  }
复制代码

300-399之间的请求码是重定向,需要重新请求,拼接到方法里了,实际这部分代码也可以在 builder 里暴露出来提供默认实现。注意第一次408超时,只会再重试1次 client.proxyAuthenticator().authenticate(route, userResponse);参见源码分析2

client.authenticator().authenticate(route, userResponse);参见源码分析2

java copyable">//当请求失败的时候,通过这个方法判断状态码,是不是要继续请求,这里只做简单的分析
 private Request followUpRequest(Response userResponse) throws IOException {
  ..........
    switch (responseCode) {
      case HTTP_PROXY_AUTH://407 设置了代理,并且需要登录代理才能继续下面的访问
        Proxy selectedProxy = route != null ? route.proxy()  : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        //返回一个登录代理的请求 
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED://401服务器需要授权,这里返回一个登录服务器的请求
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT://308
      case HTTP_TEMP_REDIRECT://307
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE://300
      case HTTP_MOVED_PERM://301
      case HTTP_MOVED_TEMP://302
      case HTTP_SEE_OTHER://303
        if (!client.followRedirects()) return null;//如果设置成不支持重定向直接返回当前 response。

        String location = userResponse.header("Location");
        if (location == null) return null;//这里的判空很严谨,按理说300的响应码都会包含这个字段
        HttpUrl url = userResponse.request().url().resolve(location);
        if (url == null) return null;
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;
        // Most redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {//判断是不是要修改 body,除了 post、get 都需要
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);// 如果是PROPFIND需要保留 body,其他的都不需要
          if (HttpMethod.redirectsToGet(method)) {//PROPFIND需要把方法给为 get
            requestBuilder.method("GET", null);
          } else {//根据maintainBody是否保留 body
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {//body 都不要了,这些 header 里标识 body 信息的都不要了
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }
        if (!sameConnection(userResponse, url)) {//认证过一次就不要再次认证了
          requestBuilder.removeHeader("Authorization");
        }
        return requestBuilder.url(url).build();
      case HTTP_CLIENT_TIMEOUT://请求超时,只重试一次
        if (!client.retryOnConnectionFailure()) {
          return null;
        }
        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }
        if (userResponse.priorResponse() != null && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          return null;//如果上次也是请求超时,这次还是就不在重试了
        }
        return userResponse.request();
      default:
        return null;
    }
  }
复制代码

BridgeInterceptor

根据body 中的信息设置 header 中的一些参数,比如数据长度,数据类型。

使用 OkHttpClient.Builder 设置过来的 CookieJar(源码分析2) 接口设置 cookie,如果需要支持设置 cookie 请调用Builder cookieJar(CookieJar cookieJar)默认不支持cookie, 这样设计可能是考虑为客户端接口使用,基本不用 cookie,少了处理 cookie 的逻辑性能上更好一些

其中 Connection:Keep-Alive 对 socket 连接缓存,socket内部会每隔一段时间检测是否和服务器连通,比如断网了,检测不到服务器在线,发送 RST 包断开连接。

请求头中的 Host 是为了让服务器定位哪个域名下的服务的,只有很少的大公司会一个 ip 下的主机部署一套域名的服务,往往是一台主机承载了很多个域名的服务,http 的请求头里只有要请求的路径。为了区分把请求下发给哪个域名需要在 header 设置 Host,框架中已经自动搞定了。大家可以试试,不带 host 的请求有的是可以走通的,但大部分是会报错的

gzip 框架内部是自动支持压缩的,这样可以减少传输的数据量。注意如果框架使用者在 Header 中设置了Accept-Encoding:gzip,框架不会自动解压,会把原始数据在 response 直接返回。保证响应内容和请求是对应的

java copyable">public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();
    if (body != null) {//如果是 post 请求,会设置 body
      MediaType contentType = body.contentType();
      if (contentType != null) {//请求体的数据类型,form 格式的 header 如 application/x-www-form-urlencoded
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {//数据长度,服务器根据数据长度读取 body
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {//输出的内容长度不能确定
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    if (userRequest.header("Host") == null) {//设置 host,一个ip会对应几个域名的情况,服务器判断header 里的 host,判断应该去哪个域名下查找数据
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    if (userRequest.header("Connection") == null) {//建立连接消耗性能,所有在服务器和客户端都有缓存,要设置这个字段
      requestBuilder.header("Connection", "Keep-Alive");
    }
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {//使用 gzip 传输数据较少很大的数据量
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {//如果实现了 cookieJar 接口,会给 header 写入 cookie
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    if (userRequest.header("User-Agent") == null) {//设置了默认的 user-agent:okhttp/3.9.0
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    Response networkResponse = chain.proceed(requestBuilder.build());//继续执行后续的请求,直到服务器返回数据
    //这个方法里只是调用了cookieJar.saveFromResponse(url, cookies);
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    //这里给码农返回的是传进来的 request,并不是添加了很多默认值之后的 request
    Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
    /**处理 gzip 的逻辑,框架使用者并没有设置 gzip 的,框架自动添加了压缩逻辑;
      *并且服务器的响应头说返回了 gzip 的数据;
      *这里解析了 body 中的数据并返回了没压缩的数据。
      *清楚 header 中的Encoding和Length,因为码农拿到的是解压后的数据
      **/
    if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")//已经不是 gzip 压缩之后的数据了
          .removeAll("Content-Length")//解压后数据长度变长了
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }
    return responseBuilder.build();
  }
复制代码

CacheInterceptor

主要处理了缓存逻辑,如果需要支持 Cache-Control、Pragma 响应头使用 cache,可以使用框架中自带的 Cache 类OkHttpClient.Builder().cache(new Cache(file,maxSize)默认不支持缓存

InternalCache(源码分析2) 获取或者保存缓存的接口,这里只处理存取的逻辑。

判断缓存是否可用的逻辑在CacheStrategy中,CacheStrategy里面对header进行了一系列的计算,这些计算并没有使用接口暴露出来,这样设计因为里面完全遵循了http协议,只要协议里所有的header参数都处理了不会有任何其他实现类,则不需要抽取为接口

java copyable">Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null? cache.get(chain.request()): null;//设置了InternalCache,根据请求获取一个缓存的 response

    long now = System.currentTimeMillis();//http 的协议需要根据当前时间是否判断使用缓存 
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();//判断 header 的缓存有效期
    Request networkRequest = strategy.networkRequest;//根据 header 判断是否需要发起请求
    Response cacheResponse = strategy.cacheResponse;//根据 header 判断是不是使用缓存的 response 
    if (cache != null) {//记录使用了这个更新,在 cache 中的实现只是对使用缓存情况计数
      cache.trackResponse(strategy);
    }
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }//在 cache 中找到了 request 缓存的 response,但是根据 header 中的时间策略,这个 response 已经超时失效了
    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {//request 的 cacheControl只允许使用缓存 onlyIfCached 为 true,响应投中包含only-if-cached
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }
    if (networkRequest == null) {//缓存还在有效期内,直接返回了缓存
      return cacheResponse.newBuilder()//返回了缓存的 respose
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    Response networkResponse = null;
    try {//如果没有缓存或者缓存失效,后面继续请求网络
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());//networkResponse是空肯定是抛异常了,没有返回值
      }
    }
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {//如果有缓存并且网络返回了304,还是要使用缓存数据
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        cache.trackConditionalCacheHit();//使用了一次缓存,记录一下
        cache.update(cacheResponse, response);//更新缓存,主要是 header 变了
        return response;
      } else {//缓存失效了,释放掉
        closeQuietly(cacheResponse.body());
      }
    }
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    if (cache != null) {//下面是存储缓存的
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        CacheRequest cacheRequest = cache.put(response);//更新缓存,header/body都变了
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {//不支持缓存方法,要清除掉缓存,post 不支持
        try {
          cache.remove(networkRequest);//
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }
    return response;
  }
复制代码

CacheStrategy.Factory

只是根据requestHeader对 CacheStrategy 需要的参数做解析,这里叫 Factory 并不合适,因为它不是工厂,还不如弄个静态方法parse,所有的逻辑处理都在get()里(下一个分析),如果对http协议缓存处理部分有一定了解,就不用看Factory的分析了

java copyable">    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;//当前时间,判断当前客户端的时间是不是可以直接使用缓存,如果客户端的时间是错的,会对缓存逻辑有影响
      this.request = request;//码农的request
      this.cacheResponse = cacheResponse;//上一次的缓存

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();//缓存的请求时间 在CallServerInterceptor中给这两个字段赋值
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();//请求的响应时间
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {//拿到缓存的response的header,找里面和缓存时间相关的值
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {//原始服务器消息发出的时间,如果服务器没有返回这个字段就使用框架中保存的receivedResponseMillis。&emsp;Date:Tue,17 Apr 2017 06:46:28GMT
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {//缓存数据的过期时间 Expires: Fri, 30 Oct 2018 14:19:41
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {//请求的数据的最后修改时间,下次请求使用If-Modified-Since传给服务器,如果数据没变化,服务器返回304。Last-modified:Tue,17 Apr 2017 06:46:28GMT
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {//数据的唯一标识,下次请求使用If-None-Match传给服务器,如果数据没变化,服务器返回304。Etag:"a030f020ac7c01:1e9f"
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {//从原始服务器到代理缓存形成的估算多少秒 Age: 12
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }
复制代码

Factory.get()主要是处理header中缓存的逻辑,判断传进来的cacheResponse是不是可以直接使用,又分为两个主要方法。

getCandidate()方法里主要是计算缓存过期时间,还有一小部分根据cache设置requestHeader的逻辑,是主要的分析方法。 response.cacheControl()方法对responseHeader服务器返回的字段(Cache-Control、Pragma)解析,把字符串转换为Bean,CacheControl里也有一个Budiler,这里有点鸡肋了,实际这个只是在header中parse就好了,绝对用不到new Builder出来。逻辑并不复杂,有兴趣自己看一下。

getCandidate

java copyable"> private CacheStrategy getCandidate() {
      if (cacheResponse == null) {//如果没有chache直接返回request策略,继续请求网络
        return new CacheStrategy(request, null);
      }
      if (request.isHttps() && cacheResponse.handshake() == null) {//如果是https请求,要有证书认证的信息,如果没有继续请求网络
        return new CacheStrategy(request, null);
      }
      if (!isCacheable(cacheResponse, request)) {//这个方法里根据响应码判断响应的数据是否可以使用缓存
        return new CacheStrategy(request, null);
      }
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {//如果reseponse中nocache,或者If-Modified-Since、If-None-Match字段则进行网络请求
        return new CacheStrategy(request, null);
      }
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {//缓存的响应头 Cache-Control的值为immutable,标识永久可以使用的缓存
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();//根据header中date、age和当前时间,计算cacheResponse距离当前时间已经过了多久了
      /*根据maxage和expires计算缓存数据还能用多久时间,
       *还根据上一次修改时间lastModified距返回数据时间Date的差10%时间以内,HTTP RFC 建议这段时间内数据是有效的*/
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {//在computeFreshnessLifetime中用if elseif优先去了maxAge的时间,这里的if判断是多余的
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }
      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {//响应头的min-fresh 最小刷新间隔
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {//如果不是must-revalidate过期之后必须请求新数据,max-stale允许过期多少秒之后,仍然可以使用缓存
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
      //上面把所有控制缓存时间的数据都准备好了,判断一下缓存数据到当前的时间 和 缓存数据最大允许的过期时间 哪个大
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {//还没有超过最大允许过期时间,但是已经超过了数据的有效期,数据过期后可以不及时刷新,返回一个警告说数据是陈旧的
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {//缓存的数据已经超过24小时了,也给一个警告,http协议不推荐设置超过24小时的缓存
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());//缓存还有效直接把缓存扔给了码农
      }

      // 客户端不能决定缓存是否可以使用,让服务器去决定,那就需要带一些参数,下面这些都是根据缓存头中服务器返回的数据标识,设置本次请求头应该带的参数,服务器知道客户端现在缓存是什么时候返回的
      String conditionValue;
      if (etag != null) {//注意这里使用的是elseif,优先级依次是 etag-lastModifity-date
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);//给请求头添加参数

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);//有缓存,但是判断不出来是否可以直接使用,让服务器去决定,如果服务器返回304,就不用返回请求体了,服务器决定使用缓存
    }
复制代码

ConnectInterceptor

在intercept中使用连接池创建链接,并根据协议获取对应的HttpCodec的实现类,主要逻辑都在newStream、connection这两个方法中。

java copyable">  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//http 协议格式的接口,直接影响了协议版本
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
复制代码

StreamAllocation

newStream 初始化connectTimeout、readTimeout、writeTimeout等全局变量,并调用findHealthyConnection找到一个可用的链接 RealConnection,在方法内 while (true) 从连接池里找到没有被占用successCount=0,并且没有close的链接,关键方法是findConnection。

java copyable">/**找到一个connect。**/
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;//是否找到了可用的RealConnection
    RealConnection result = null;//和foundPooledConnection对应,将要使用的connect
    Route selectedRoute = null;//生成RealConnection用到的Address、Proxy存到这个类中
    Connection releasedConnection;//将要释放的链接
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      releasedConnection = this.connection;//默认全局保存的connection是不可用的了,需要释放,从newStream进来是null
      toClose = releaseIfNoNewStreams();//判断connection是否需要释放,如果已经不可用把connection置空,并返回sockte准备释放
      if (this.connection != null) {//经过上面releaseIfNoNewStreams的方法判断,connection还可用没有置空
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {//没有需要释放的 连接
        releasedConnection = null;
      }

      if (result == null) {//不能直接使用connection,从缓存池里去一个,这里route是空,先当没有代理处理
        Internal.instance.get(connectionPool, address, this, null);//里面调用了acquire,如果缓存池里有可用的,就赋值给connection
        if (connection != null) {//使用了缓存池中的 connect 不用重新创建。
          foundPooledConnection = true;
          result = connection;
        } else {//缓存池中没取到,需要重新创建连接,如果有代理,使用route取一个有代理的试试
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {//释放掉不可用的
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {//上面已经从连接池缓存中找到了可用的
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      return result;
    }

    boolean newRouteSelection = false;//缓存中没有返回一个可用的RealConnection,需要生成一个
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {//判断刚才的全局保存的route是否为空,如果还没有赋值 就使用routeSelector找一个
      newRouteSelection = true;
      routeSelection = routeSelector.next();//routeSelector中保存了所有的代理(可能多个),从中找到一个可用的并调用 dns,找到要连接的 ip,准备建立 socket 连接
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {//Selection中返回了所有可用的route,从头往后找,越靠前的代理连通性越高
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);//掉用了connectPool.get在方法isEligible中判断是否有和 address、route对应的缓存 Connection,如果有调用 StreamAllocation.acquire给全局变量connection赋值
          if (connection != null) {//上面的Internal.instance.get取到了缓存的 connection直接返回
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }
      if (!foundPooledConnection) {//没有取到缓存
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);//缓存中没有,只能创建一个新的
        acquire(result, false);
      }
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);//如果缓存中取到了,调用监听,这个方法应该在上面的 if (!foundPooledConnection) 前面就好理解了
      return result;//和上面的 if 比 这里 return 了,不会创建 socket连接了
    }
    //这里是调用socket.connect的主要方法,并调用connectTls方法对 https进行 Handshake并调用 HostnameVerifier.verify 验证证书, 只有新创建的才会进入这里,在RealConnection中会详细分析
    result.connect( connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {//把这个RealConnection缓存到连接池里
      reportedAcquired = true;

      Internal.instance.put(connectionPool, result);
      if (result.isMultiplexed()) {//是http2.0协议
        socket = Internal.instance.deduplicate(connectionPool, address, this);//调用了releaseAndAcquire把connection重新赋值,并释放旧的socket
        result = connection;
      }
    }
    closeQuietly(socket);
    eventListener.connectionAcquired(call, result);
    return result;
  }
复制代码

RealConnection

HostnameVerifier.verify验证tls证书 (源码分析2)

java copyable"> public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();//选择https的算法用
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {//判断 ssl 的安全性,不支持 CLEARTEXT
        throw new RouteException(new UnknownServiceException("CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException("CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    }

    while (true) {
      try {
        if (route.requiresTunnel()) {//如果是https 并且含有代理,需要先创建代理通道
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);//先发出一个请求创建通道
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {//只创建一个socket
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        establishProtocol(connectionSpecSelector, call, eventListener);//判断http协议版本,链接socket,并建立 https 使用的加密通道,使用HostnameVerifier.verify验证tls证书
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {//创建链接失败,释放资源
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

        eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }
    if (route.requiresTunnel() && rawSocket == null) {//链接太多了
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: " + MAX_TUNNEL_ATTEMPTS);
      throw new RouteException(exception);
    }
    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();//限制http2.0同一个connection的最大的连接数
      }
    }
  }
复制代码

CallServerInterceptor

已经在上面的步骤创建好了连接,下面就可以直接写入数据了,使用Http1Codec按照 http1.1的格式写入数据到 socket 中,使用Http2Codec按照 http2的格式写入数据,注意这里返回的 Response持有的 Body是已流的形式存在的,使用完后一定要调用 close,否则连接池等资源不能释放。

java copyable">public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();//在ConnectInterceptor中建立连接后赋值,
    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);//把 request 中的 header按照 http 协议的各个版本拼接好写入到 socket 中
    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.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();//如果 get 请求数据大于1k 当前请求不能把数据全部写入,需要分次
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      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 (!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();//请求数据全部写入完成,等待服务器响应

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());//这个 listener的位置在服务器响应之前,并不是已经开始读取 response 了
      responseBuilder = httpCodec.readResponseHeaders(false);//读取响应头
    }
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();//生成了请求的结果

    realChain.eventListener() .responseHeadersEnd(realChain.call(), response);//在responseHeadersStart和responseHeadersEnd之间包含了读取响应头和服务器处理请求的时间
    int code = response.code();//响应码
    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 = response.newBuilder()
          .body(httpCodec.openResponseBody(response))//读取响应体到FixedLengthSource 或者ChunkedSource 中 使用完必须调用调用close,否则资源不能释放。
          .build();
    }
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();//响应头中包含了close需要释放连接
    }
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {//204、205不需要返回请求体
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;//已经完成了返回了这次请求的响应
复制代码

add by dingshaoran


http://www.niftyadmin.cn/n/1496382.html

相关文章

741元打六折怎么用计算机,2018年秋七年级数学上册第4章一元一次方程4.3用一元一次方程解决问题4.3.6打折销售问题练习(...

309教育网http://doc.wendoc.com309教育资源库http://doc.wendoc.com一元一次方程第6课时打折销售问题知|识|目|标1&#xff0e;通过实例理解售价、成本、利润、折扣率的真实含义和它们之间的关系&#xff0c;会用一元一次方程解决打折销售问题&#xff0e;2&#xff0e;通过实…

ajax的工作原理

1、ajax的概念介绍2、原生js创建ajax对象3、前后端常用的数据交互模式4、同源跨域概念

gulp.src html用法,gulp插件使用

一、gulp-htmlmin:html文件压缩1.下载插件npm install gulp-htmlminnpm install gulp-file-include2.gulpfile.js中引用插件const htmlmin require(gulp-htmlmin);const fileinclude require(gulp-file-include);3.编写任务&#xff0c;调用插件//html任务//1.html文件中代码…

编译器错误信息: CS1056: 意外的字符的处理办法

错误产生&#xff1a; 在使用VS2015编译一个之前存在的程序代码时&#xff0c;总是会报错。莫名的 在某些类文件的末尾添加一些[][][][]&#xff0c;提示Unexpected character ‘&#xff0c;程序出错。原因分析&#xff1a;由于C#代码中出现特殊全角字符&#xff0c;从而产…

PCL Save VTK File With Texture Coordinates 使用PCL库来保存带纹理坐标的VTK文件

我之前有一篇博客Convert PLY to VTK Using PCL 1.6.0 or PCL 1.8.0 使用PCL库将PLY格式转为VTK格式展示了如何将PLY格式文件转化为VTK格式的文件&#xff0c;在文章的最后提到了VTK文件保存纹理的两种方式&#xff0c;第一种是需要有texture的图片&#xff0c;然后每个点存储上…

用计算机求导,求导公式?

、公式法例如∫x^ndxx^(n1)/(n1)C∫dx/xlnxC∫cosxdxsinx等不定积分公式都应牢记&#xff0c;对于基本函数可直接求出原函数。2、换元法对于∫f[g(x)]dx可令tg(x),得到xw(t),计算∫f[g(x)]dx等价于计算∫f(t)w(t)dt。例如计算∫e^(-2x)dx时令t-2x,则x-1/2t,dx-1/2dt,代入后得&a…

jQuery通过Ajax向PHP服务端发送请求并返回JSON数据

服务端PHP读取MYSQL数据&#xff0c;并转换成JSON数据&#xff0c;传递给前端Javascript&#xff0c;并操作JSON数据。本文将通过实例演示了jQuery通过Ajax向PHP服务端发送请求并返回JSON数据。阅读本文的读者应该具备jQuery、Ajax、PHP相关知识&#xff0c;并能熟练运用。 JSO…

国防科大天河计算机应用,国防科大天河二号位居世界超级计算机TOP500榜首

据新华社广州 6月29日电(记者 陈冀)国家超级计算广州中心应用推广大会6月29日在广州召开&#xff0c;科学技术部副部长曹健林向广州超级计算中心授予了“国家超级计算广州中心”和“中国(广州)计算科学服务中心”牌匾&#xff0c;这标志着广州跻身国家级超级计算中心行列。目前…