OKHTTP-拦截器专题

RetryAndFollowUpInterceptor 重试以及重定向拦截器

为什么其他拦截器都是新建的,只有它是一直跟着拦截器对象的构造函数?

  1. 重试以及重定向

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

Request request = chain.request();

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Call call = realChain.call();

EventListener eventListener = realChain.eventListener();

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),

createAddress(request.url()), call, eventListener, callStackTrace);

this.streamAllocation = streamAllocation;

int followUpCount = 0;

Response priorResponse = null;

//OKHTTP重试拦截器会一直进行循环

while (true) {

if (canceled) {

streamAllocation.release();

throw new IOException("Canceled");

}

Response response;

boolean releaseConnection = true;

try {

//请求如果出现了异常就会抛出异常

response = realChain.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(), streamAllocation, false, request)) {

throw e.getFirstConnectException();

}

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

//recover 来判断是否需要重试

if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;

releaseConnection = false;

continue;

} finally {

// We're 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();

}

Request followUp;

try {

//判断是否需要重定向

followUp = followUpRequest(response, streamAllocation.route());

} catch (IOException e) {

streamAllocation.release();

throw e;

}

if (followUp == null) {

if (!forWebSocket) {

streamAllocation.release();

}

return response;

}

closeQuietly(response.body());

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());

}

if (!sameConnection(response, followUp.url())) {

streamAllocation.release();

streamAllocation = new StreamAllocation(client.connectionPool(),

createAddress(followUp.url()), call, eventListener, callStackTrace);

this.streamAllocation = streamAllocation;

} else if (streamAllocation.codec() != null) {

throw new IllegalStateException("Closing the body of " + response

+ " didn't close its backing stream. Bad interceptor?");

}

request = followUp;

priorResponse = response;

}

重试判定的方法

private boolean recover(IOException e, StreamAllocation streamAllocation,

boolean requestSendStarted, Request userRequest) {

streamAllocation.streamFailed(e);

// The application layer has forbidden retries.

//是否配置了全局okhttpcontient?

if (!client.retryOnConnectionFailure()) return false;

//是否配置了这次请求的okhttpcontient?

// We can't send the request body again.

if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;

//是不是重试的异常?协议的异常?

// This exception is fatal.

if (!isRecoverable(e, requestSendStarted)) return false;

//不存在更多的路由,如果有其他路线会帮你重试

// No more routes to attempt.

if (!streamAllocation.hasMoreRoutes()) return false;

// For failure recovery, use the same route selector with a new connection.

return true;

}

private boolean isRecoverable(IOException e, boolean requestSendStarted) {

// If there was a protocol problem, don't recover.

if (e instanceof ProtocolException) {

return false;

}

// If there was an interruption don't recover, but if there was a timeout connecting to a route

// we should try the next route (if there is one).

if (e instanceof InterruptedIOException) {

return e instanceof SocketTimeoutException && !requestSendStarted;

}

// Look for known client-side or negotiation errors that are unlikely to be fixed by trying

// again with a different route.

//有可能是证书损坏问题

if (e instanceof SSLHandshakeException) {

// If the problem was a CertificateException from the X509TrustManager,

// do not retry.

if (e.getCause() instanceof CertificateException) {

return false;

}

}

//证书校验失败不匹配或者过期

if (e instanceof SSLPeerUnverifiedException) {

// e.g. a certificate pinning error.

return false;

}

  1. 默认拦截器桥接拦截器

BridgeInterceptor 主要是将请求连接在一起

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) {

//默认长链接

//大部分链接都是短链接为什么这里使用Keep-Alive?

//当前OKHTTP是socket的一个封装,我们利用Keep-Alive来保持socket不断开,不需要每次都发起新的请求

requestBuilder.header("Connection", "Keep-Alive");

}

// If we add an "Accept-Encoding: gzip" header field we're 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");

}

//加载URL请求头 登录后拿到Cookie才能继续请求 (有些请求需要登陆才能请求)

//cookie 需要自己保存,如果需要的话在Buidler里建立

List<Cookie> 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());

}

//继续执行责任链拿到networkResponse

//继续执行让我们保存cookie

Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()

.request(userRequest);

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

String contentType = networkResponse.header("Content-Type");

responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));

}

return responseBuilder.build();

  1. 缓存拦截器

get请求能缓存,post请求是不能缓存的

如果要使用okhttp的缓存要使用cache

/** Sets the response cache to be used to read and write cached responses. */

public Builder cache(@Nullable Cache cache) {

this.cache = cache;

this.internalCache = null;

return this;

}

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

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;

//networkRequest + 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) {

//networkRequest + cacheResponse可以判断需不需要直接发起请求或者使用缓存

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're doing a conditional get.

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();

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

}

/**

* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.

*/

public CacheStrategy get() {

CacheStrategy candidate = getCandidate();

//如果缓存为空就直接返回了

if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {

// We're forbidden from using the network and the cache is insufficient.

return new CacheStrategy(null, null);

}

return candidate;

}

  1. 缓存拦截器

private CacheStrategy getCandidate() {

// No cached response.

//通过url进行查找数据,从文件缓存中进行查找(get请求才有缓存)

if (cacheResponse == null) {

//之前传入的cacheResponse现在才作为判断依据

return new CacheStrategy(request, null);

}

//如果有请求但是没有握手信息,就进行网络请求

// Drop the cached response if it's missing a required handshake.

if (request.isHttps() && cacheResponse.handshake() == null) {

return new CacheStrategy(request, null);

}

// If this response shouldn't have been stored, it should never be used

// as a response source. This check should be redundant as long as the

// persistence store is well-behaved and the rules are constant.

//通过相应码判断能不能缓存,不能缓存就发起网络请求

if (!isCacheable(cacheResponse, request)) {

return new CacheStrategy(request, null);

}

//如果请求包含CacheControl:no-cache 需要与服务器验证缓存有效性

If-Modified-Since If-None-Match

//请求头只要存在三者任意一个,进行请求

CacheControl requestCaching = request.cacheControl();

if (requestCaching.noCache() || hasConditions(request)) {

return new CacheStrategy(request, null);

}

//如果缓存,CacheControl:immutable 响应内容将一直不变,可以使用缓存

CacheControl responseCaching = cacheResponse.cacheControl();

if (responseCaching.immutable()) {

return new CacheStrategy(null, cacheResponse);

}

//获取缓存的响应从创建到现在的时间

long ageMillis = cacheResponseAge();

//获取有效缓存的时长

long freshMillis = computeFreshnessLifetime();

if (requestCaching.maxAgeSeconds() != -1) {

freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));

}

long minFreshMillis = 0;

if (requestCaching.minFreshSeconds() != -1) {

minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());

}

long maxStaleMillis = 0;

if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {

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()) {

builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");

}

return new CacheStrategy(null, builder.build());

}

// Find a condition to add to the request. If the condition is satisfied, the response body

// will not be transmitted.

String conditionName;

String conditionValue;

if (etag != null) {

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

  1. 连接拦截器

Socket和服务器IP/PORT绑定

public ConnectionPool() {

this(5//帮我们最大缓存5个, 5//超过5分钟没有响应就认为是闲置, TimeUnit.MINUTES);

}

//保存连接加以复用

void put(RealConnection connection) {

assert (Thread.holdsLock(this));

if (!cleanupRunning) {

cleanupRunning = true;

//首先先进行清理

executor.execute(cleanupRunnable);

}

connections.add(connection);

}

private final Runnable cleanupRunnable = new Runnable() {

@Override public void run() {

while (true) {

long waitNanos = cleanup(System.nanoTime());

if (waitNanos == -1) return;

if (waitNanos > 0) {

long waitMillis = waitNanos / 1000000L;

waitNanos -= (waitMillis * 1000000L);

synchronized (ConnectionPool.this) {

try {

ConnectionPool.this.wait(waitMillis, (int) waitNanos);

} catch (InterruptedException ignored) {

}

}

}

}

}

};

long cleanup(long now) {

int inUseConnectionCount = 0;

int idleConnectionCount = 0;

RealConnection longestIdleConnection = null;

long longestIdleDurationNs = Long.MIN_VALUE;

// Find either a connection to evict, or the time that the next eviction is due.

synchronized (this) {

//遍历连接池里闲置的进行清理操作

//在第六个加入时先添加入连接池

for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {

RealConnection connection = i.next();

// If the connection is in use, keep searching.

if (pruneAndGetAllocationCount(connection, now) > 0) {

//在用的有多少

inUseConnectionCount++;

continue;

}

//不在用的有多少

idleConnectionCount++;

// If the connection is ready to be evicted, we're done.

//记录一个闲置时间

//

long idleDurationNs = now - connection.idleAtNanos;

if (idleDurationNs > longestIdleDurationNs) {

longestIdleDurationNs = idleDurationNs;

longestIdleConnection = connection;

}

}

//闲置时间大于了5分钟

if (longestIdleDurationNs >= this.keepAliveDurationNs

|| idleConnectionCount > this.maxIdleConnections) {

// We've found a connection to evict. Remove it from the list, then close it below (outside

// of the synchronized block).

//移除池

//第六个超过了5个即移除

connections.remove(longestIdleConnection);

} else if (idleConnectionCount > 0) {

// A connection will be ready to evict soon.

//超过了包活时间就移除

return keepAliveDurationNs - longestIdleDurationNs;

} else if (inUseConnectionCount > 0) {

// All connections are in use. It'll be at least the keep alive duration 'til we run again.

//正在使用中等5分钟再查-

return keepAliveDurationNs;

} else {

// No connections, idle or in use.

cleanupRunning = false;

return -1;

}

}

closeQuietly(longestIdleConnection.socket());

// Cleanup again immediately.

return 0;

}

  1. 请求拦截器

CallServerInterceptor

将请求信息封装成信息,发送给服务器

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值