博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OKHttp源码解析(1)----整体流程
阅读量:6228 次
发布时间:2019-06-21

本文共 19726 字,大约阅读时间需要 65 分钟。

系列文章

1.简介

Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient,HttpURLConnection相对来说比HttpClient难用,google自从2.3版本之后一直推荐使用HttpURLConnection,并且在6.0版本的sdk中直接删掉了HttpClient类。Android4.4的源码中可以看到OkHttp替换了HttpURLConnection

相比以上两种通信类,OKHttp有巨大的优势:

  • 支持HTTP/2,HTTP/2通过使用多路复用技术,在一个单独的TCP链接上支持并发,通过在一个连接上一次性发送多个请求来发送或接收数据
  • 如果HTTP/2不可用,链接池减少请求延迟
  • 支持GZIP,额可以压缩下载体积
  • 响应缓存可以避免重复请求网络
  • 会从很多常用的连接问题中自动恢复,如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OKHttp会尝试下一个IP地址
  • OKHttp还处理了代理服务器问题和SSL握手失败问题

流程图:

(图1)

2.使用

2.1 构建OkHttpClient

OkHttpClient(Builder builder) {    this.dispatcher = builder.dispatcher;   //调度器,主要用于异步网络请求的控制    this.proxy = builder.proxy;  //代理    this.protocols = builder.protocols;  //协议    this.connectionSpecs = builder.connectionSpecs;  //指定配置(tls版本、密码组等)    this.interceptors = Util.immutableList(builder.interceptors);  //用户自定义拦截器    this.networkInterceptors = Util.immutableList(builder.networkInterceptors); //用户自定义网络拦截器    this.eventListenerFactory = builder.eventListenerFactory;    this.proxySelector = builder.proxySelector;    this.cookieJar = builder.cookieJar;  //    this.cache = builder.cache;  //缓存    this.internalCache = builder.internalCache;  //内部缓存    this.socketFactory = builder.socketFactory;    boolean isTLS = false;    for (ConnectionSpec spec : connectionSpecs) {      isTLS = isTLS || spec.isTls();    }    if (builder.sslSocketFactory != null || !isTLS) {      this.sslSocketFactory = builder.sslSocketFactory;      this.certificateChainCleaner = builder.certificateChainCleaner;    } else {      X509TrustManager trustManager = systemDefaultTrustManager();      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);    }    this.hostnameVerifier = builder.hostnameVerifier;    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(        certificateChainCleaner);    this.proxyAuthenticator = builder.proxyAuthenticator;    this.authenticator = builder.authenticator;    this.connectionPool = builder.connectionPool;  //链接池    this.dns = builder.dns;  //dns    this.followSslRedirects = builder.followSslRedirects;  //是否支持http与https之间互相重定向    this.followRedirects = builder.followRedirects;  //否是支持重定向    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;  //连接失败重试    this.connectTimeout = builder.connectTimeout;  //连接超时时间    this.readTimeout = builder.readTimeout;  //读取超时时间    this.writeTimeout = builder.writeTimeout;  //写入超时时间    this.pingInterval = builder.pingInterval;  }复制代码

2.2 get请求:

OkHttpClient client = new OkHttpClient();    public String get(String url) throws IOException {        Request request = new Request.Builder()                .url(url)                .build();        Response response = client.newCall(request).execute();        return response.body().string();    }复制代码

2.3 post请求:

OkHttpClient client = new OkHttpClient();       public String post(String url,String json) throws IOException {        RequestBody body = RequestBody.create(JSONObject,json);        Request request = new Request.Builder()                .post(body)                .url(url)                .build();        Response response = client.newCall(request).execute();        return response.body().string();    }复制代码

3.流程

以下以同步的get请求来分析整体的请求过程

Response response = client.newCall(request).execute();复制代码

OkHttpClient 首先创建一个Call(RealCall):

@Override public Call newCall(Request request) {    return new RealCall(this, request, false /* for web socket */);  }复制代码

查看RealCall的execute()方法:

@Override public Response execute() throws IOException {    synchronized (this) {      if (executed) throw new IllegalStateException("Already Executed");      executed = true;    }    captureCallStackTrace();    try {      client.dispatcher().executed(this);   //记录此请求      Response result = getResponseWithInterceptorChain();  //请求链及拦截器      if (result == null) throw new IOException("Canceled");      return result;    } finally {      client.dispatcher().finished(this);    }  }复制代码

在RealCall的execute()方法中,有两步比较重要: client.dispatcher().executed(this);OKHttp的分发器在请求的开始会记录此次请求,请求结束后会移除。分发器在OKHttp的异步请求中比较中要,下面再讲。

Response result = getResponseWithInterceptorChain()OKHttp完整的网络请求就是在这步中进行的。下面我们来看下其中做了什么?

Response getResponseWithInterceptorChain() throws IOException {    List
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); return chain.proceed(originalRequest); }复制代码

OKHttp的完整请求就在这里,请求和返回数据的处理就是由这些拦截器来完成的,各个拦截器由RealInterceptorChain进行串联。如流程图图1。具体的串联实现,可以看下源码。

4.拦截器

OKHttp的拦截器主要是下面几种:

  • client.interceptors()
  • retryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • client.networkInterceptors()
  • CallServerInterceptor

4.1 client.interceptors()

APP层面的拦截器(Application Interception 应用拦截器),是在配置OKHttpClient时设置的interceptors

  • 对请求参数进行统一的加密处理
  • 拦截不符合规则的URL
  • 对请求或者返回参数设置统一的编码方式
  • 其他

4.2 RetryAndFollowUpInterceptor

重试与重定向拦截器,负责重试及重定向。现在我们来分析源码:

@Override public Response intercept(Chain chain) throws IOException {    Request request = chain.request();    streamAllocation = new StreamAllocation(        client.connectionPool(), createAddress(request.url()), callStackTrace);    int followUpCount = 0;    Response priorResponse = null;    //while循环进行重试,请求成功则返回response;请求失败,则对responseCode判断,并更改相应设置,进行重新请求。    while (true) {      if (canceled) {        streamAllocation.release();        throw new IOException("Canceled");      }      Response response = null;      boolean releaseConnection = true;      try {      //进行链式请求,并对请求结果进行判定是否重试        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);        releaseConnection = false;      } catch (RouteException e) {        // The attempt to connect via a route failed. The request will not have been sent.        // 路由连接失败,请求将不会被发送        if (!recover(e.getLastConnectException(), false, request)) {          throw e.getLastConnectException();        }        releaseConnection = false;        continue;      } catch (IOException e) {        // An attempt to communicate with a server failed. The request may have been sent.         // 服务器连接失败,请求可能已被发送        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);        if (!recover(e, requestSendStarted, request)) throw e;        releaseConnection = false;        continue;      } finally {        // We are throwing an unchecked exception. Release any resources.        // 抛出未检查的异常,释放资源        if (releaseConnection) {          streamAllocation.streamFailed(null);          streamAllocation.release();        }      }      // Attach the prior response if it exists. Such responses never have a body.      if (priorResponse != null) {        response = response.newBuilder()            .priorResponse(priorResponse.newBuilder()                    .body(null)                    .build())            .build();      }      //对response进行判定,是否进行重定向重试      Request followUp = followUpRequest(response);      //followUp为空,表明不是需要重定向的responseCode,返回结果response(可能是请求成功,也可能是请求失败)      if (followUp == null) {        if (!forWebSocket) {          streamAllocation.release();        }        return response;      }       //释放response.body()      closeQuietly(response.body());       //重定向超过最大次数(20次),抛出异常      if (++followUpCount > MAX_FOLLOW_UPS) {        streamAllocation.release();        throw new ProtocolException("Too many follow-up requests: " + followUpCount);      }      if (followUp.body() instanceof UnrepeatableRequestBody) {        streamAllocation.release();        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());      }             // response 和 followUp 比较是否为同一个连接       // 若为重定向就销毁旧连接,创建新连接      if (!sameConnection(response, followUp.url())) {        streamAllocation.release();        streamAllocation = new StreamAllocation(            client.connectionPool(), createAddress(followUp.url()), callStackTrace);      } else if (streamAllocation.codec() != null) {        throw new IllegalStateException("Closing the body of " + response            + " didn't close its backing stream. Bad interceptor?");      }      // 将重定向操作得到的新请求设置给 request      request = followUp;      priorResponse = response;    }  }复制代码

4.3 BridgeInterceptor

桥接拦截器(内容拦截器),负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应。

@Override public Response intercept(Chain chain) throws IOException {      // 将用户友好的 request 构造为发送给服务器的 request    Request userRequest = chain.request();    Request.Builder requestBuilder = userRequest.newBuilder();    RequestBody body = userRequest.body();    // 若有请求体,则构造    if (body != null) {      MediaType contentType = body.contentType();      if (contentType != null) {        requestBuilder.header("Content-Type", contentType.toString());      }      long contentLength = body.contentLength();      if (contentLength != -1) {        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) {      requestBuilder.header("Host", hostHeader(userRequest.url(), false));    }    if (userRequest.header("Connection") == null) {      requestBuilder.header("Connection", "Keep-Alive");    }    // If we add an "Accept-Encoding: gzip" header field we are responsible for also decompressing    // the transfer stream.    boolean transparentGzip = false;    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {      transparentGzip = true;      requestBuilder.header("Accept-Encoding", "gzip");    }       // 设置 cookie    List
cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } // 构造完后,将 request 交给下一个拦截器去处理。最后又得到服务端响应 networkResponse Response networkResponse = chain.proceed(requestBuilder.build()); // 保存 networkResponse 的 cookie HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); // 将 networkResponse 构造为对用户友好的 response Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); // 如果 networkResponse 使用 gzip 并且有响应体的话,给用户友好的 response 设置响应体 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") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); }复制代码

4.4 CacheInterceptor

缓存拦截器,负责读取缓存直接返回、更新缓存。当网络请求有符合要求的Cache时,直接返回Cache。如果当前Cache失效,则删除。CacheStrategy:缓存策略,CacheStrategy类是一个非常重要的类,用于控制请求是网络获取还是缓存获取。

@Override public Response intercept(Chain chain) throws IOException {     // 得到 request 对应缓存中的 response    Response cacheCandidate = cache != null        ? cache.get(chain.request())        : null;          // 获取当前时间,会和之前缓存的时间进行比较    long now = System.currentTimeMillis();     // 得到缓存策略    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();    Request networkRequest = strategy.networkRequest;    Response cacheResponse = strategy.cacheResponse;     // 追踪缓存,其实就是计数    if (cache != null) {      cache.trackResponse(strategy);    }     // 缓存不适用,关闭    if (cacheCandidate != null && cacheResponse == null) {      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.    }    // If we're forbidden from using the network and the cache is insufficient, fail.     // 禁止网络并且没有缓存的话,返回失败    if (networkRequest == null && cacheResponse == null) {      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 we don't need the network, we're done.     //不用网络请求,返回缓存    if (networkRequest == null) {      return cacheResponse.newBuilder()          .cacheResponse(stripBody(cacheResponse))          .build();    }    Response networkResponse = null;    try {      networkResponse = chain.proceed(networkRequest);    } finally {      // If we're crashing on I/O or otherwise, don't leak the cache body.      if (networkResponse == null && cacheCandidate != null) {        closeQuietly(cacheCandidate.body());      }    }    // If we have a cache response too, then we are doing a conditional get.     // 如果我们同时有缓存和 networkResponse ,根据情况使用    if (cacheResponse != null) {      if (networkResponse.code() == HTTP_NOT_MODIFIED) {        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();        // Update the cache after combining headers but before stripping the        // Content-Encoding        header (as performed by initContentStream()).        // 更新原来的缓存至最新        cache.trackConditionalCacheHit();        cache.update(cacheResponse, response);        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)) {        // Offer this request to the cache.        CacheRequest cacheRequest = cache.put(response);        return cacheWritingResponse(cacheRequest, response);      }      if (HttpMethod.invalidatesCache(networkRequest.method())) {        try {          cache.remove(networkRequest);        } catch (IOException ignored) {          // The cache cannot be written.        }      }    }    return response;  }复制代码

4.5 ConnectInterceptor

连接拦截器,负责和服务器建立连接

@Override public Response intercept(Chain chain) throws IOException {    RealInterceptorChain realChain = (RealInterceptorChain) chain;    Request request = realChain.request();    StreamAllocation streamAllocation = realChain.streamAllocation();    // We need the network to satisfy this request. Possibly for validating a conditional GET.    boolean doExtensiveHealthChecks = !request.method().equals("GET");     //在连接池中找个可用的链接,并创建HttpCodec    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);    RealConnection connection = streamAllocation.connection();    return realChain.proceed(request, streamAllocation, httpCodec, connection);  }复制代码

链接拦截器主要是完成了网络设施的建设,包含连接connection,根据socket生成输出流sink和输入流source。 后面的CallServerInterceptor使用这些设施完成请求的发送以及响应的读取。这些请求过程,我们将在CallServerInterceptor一章中详细解读。

4.6 client.networkInterceptors()

网络请求层面的拦截器,配置OkHttpClient时设置的networkInterceptors:

请求时:

  • 可以修改OkHttp框架自动添加的一些属性
  • 可以观察最终完整的请求参数

返回结果:

  • 能够对中间的响应进行操作,比如重定向和重试
  • 当发生网络短路时,不调用缓存的响应结果
  • 监控数据,就像数据在网络上传输一样
  • 访问承载请求的连接Connection。

4.7 CallServerInterceptor

请求服务拦截器,负责向服务器发送请求数据、从服务器读取响应数据

@Override public Response intercept(Chain chain) throws IOException {    RealInterceptorChain realChain = (RealInterceptorChain) chain;    HttpCodec httpCodec = realChain.httpStream();    StreamAllocation streamAllocation = realChain.streamAllocation();    RealConnection connection = (RealConnection) realChain.connection();    Request request = realChain.request();    long sentRequestMillis = System.currentTimeMillis();     // 整理请求头并写入    httpCodec.writeRequestHeaders(request);    Response.Builder responseBuilder = null;     // 检查是否为有 body 的请求方法    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {      // If there is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100      // Continue" response before transmitting the request body. If we do not 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();        responseBuilder = httpCodec.readResponseHeaders(true);      }      if (responseBuilder == null) {        // Write the request body if the "Expect: 100-continue" expectation was met.        // 写入请求体        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);        request.body().writeTo(bufferedRequestBody);        bufferedRequestBody.close();      } 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) {      responseBuilder = httpCodec.readResponseHeaders(false);    }    Response response = responseBuilder        .request(request)        .handshake(streamAllocation.connection().handshake())        .sentRequestAtMillis(sentRequestMillis)        .receivedResponseAtMillis(System.currentTimeMillis())        .build();    int code = response.code();     // 如果为 web socket 且状态码是 101 ,那么 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 {      response = response.newBuilder()          .body(httpCodec.openResponseBody(response))          .build();    }     // 如果请求头中有 close 那么断开连接    if ("close".equalsIgnoreCase(response.request().header("Connection"))        || "close".equalsIgnoreCase(response.header("Connection"))) {      streamAllocation.noNewStreams();    }     // 抛出协议异常    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {      throw new ProtocolException(          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());    }    return response;  }复制代码

小结

本章主要讲了OkHttp的整体流程,并对源码的关键点进行了备注。详细的讲解在各个拦截器的单章讲解中,开发者可以在后面几章查看。

OkHttp使用分层的思想,每一个拦截器就是一个完成相关功能的任务层,并使用链Chain串联整个流程,使用了责任链模式。在整个链中,向用户提供了插入点,用户可以自定义拦截器,完成一些自定义的加密、数据统计等相关功能。

References

转载于:https://juejin.im/post/5c065352f265da616e4c3582

你可能感兴趣的文章
c++ 快速排序
查看>>
Linux下删除命令 硬盘空间查看... 常用命令
查看>>
从客户端中检测到有潜在危险的 Request.Form 值
查看>>
Node.js制作爬取简书内容的爬虫
查看>>
编辑器之神-vim
查看>>
highcharts 柱形堆叠图
查看>>
在vue2.x中安装sass并配置
查看>>
密钥分散算法
查看>>
Django ORM字段和字段参数
查看>>
HDU-6170 Two strings
查看>>
URL和URI
查看>>
3.12DAYUP
查看>>
算法10-----分糖果
查看>>
zoj 1009
查看>>
STL之迭代器
查看>>
kubernetes删除pod失败
查看>>
防盗链之URL参数签名 总结
查看>>
中间件和上下文处理器、djangoAdmin
查看>>
grunt入门讲解1:grunt的基本概念和使用
查看>>
网页内连续英文或符号时 强制折行与不折行的解决方案
查看>>