OkHttp 차단기(2)
차단기를 통해 OkHttp의 네트워크 접근 과정을 분해하고 모든 차단기는 그 직무를 전담한다.모든 차단기는 Interceptor 인터페이스를 실현했다
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
Connection connection();
}
}
자, 이 차단기들이 무엇을 했는지 살펴보겠습니다. 위의 인터페이스에서 우리는 Response proceed (Request request)throws IO Exception 방법에만 관심을 갖습니다.
하나, 차단기 다시 시도하기 Retry AndFollowUpInterceptor
@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 (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;
Log.i("xxx","retry "+e.toString() );
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;
Log.i("xxx","retry io "+e.toString() );
continue;
} finally {
Log.i("xxx","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 = followUpRequest(response);
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()), callStackTrace);
} 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;
}
}
1, Volley의 재시도 메커니즘과 유사합니다. 우선 응답을 얻지 못하고 이상이 발생하면 다시 시도할 수 있습니다.
2, 다음에 취소 여부를 판단할 것입니다. 취소하면 이상을 던지고 AsyncCall의execute 방법에서 실패한 방법responseCallback을 직접 리셋합니다.onFailure(RealCall.this, e);
3, response = (RealInterceptorChain) chain을 사용합니다.proceed(request, streamAllocation, null, null); 다음 차단기를 차례로 호출하여 응답을 가져옵니다. 이상이 발생하면 복구 함수를 통해 다시 시도할 수 있는지 여부를 판단합니다. 그렇지 않으면 계속 아래로 실행합니다.다음은 Recover 함수를 보십시오. 안에 네 가지 상황을 열거하였는데 다시 시도할 수 없습니다
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// 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;
}
A, 재시도 금지 B, 재송신 요청체 C를 설정했는데 심각한 오류가 발생했습니다. 이런 심각한 오류를 다시 시도할 수 없습니다. isRecoverable 방법의 if 판단에서 제시되었습니다. 예를 들어 프로토콜 오류, 중단, 연결 시간 초과, SSL 악수 오류 등입니다.
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;
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true;
}
D, 더 이상 시도하는 루트가 없습니다
4, 순조롭게 응답을 받으면 Request follow Up = follow Up Request(response)를 통과합니다.재지정 여부를 판단하다
둘째, 브리지 차단기 Bridge Interceptor
브리지 인터셉터 브리지 인터셉터는 비교적 간단하고 주로 두 가지 일을 한다
public Response intercept(Chain chain) throws IOException {
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'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");
}
List cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", "sss");
// requestBuilder.header("User-Agent", Version.userAgent());
}
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
위에서 요청 헤더를 구성할 때 주의해야 할 것은 Content-Encoding과 Content-Length가 동시에 나타날 수 없다는 것이다.
3, 캐시 차단기 CacheInterceptor
캐시 차단기를 잘 이해하려면 Http 캐시 메커니즘에 대해 알아야 한다. 특히 요청과 응답에 관련된 요청 헤더에는 캐시 시간을 제어하는 Cache-Control, Expires 및 캐시 서버 재검증을 위한 If-Modify-Since, If-None-Match와 메모리 신선도를 늦추는, no-cache, no-store, max-age, if-only-cache 등이 참고할 수 있다.
다음은 핵심 코드를 붙여주세요.
@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;
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're doing a conditional get.
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 (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
1, 우선 Request의 URL에 따라 캐시에서 캐시를 가져옵니다cacheCandidate 2, 요청 Request와 캐시에서 가져온cacheCandidate(공일 수 있음)에 따라 캐시 정책 캐시 Strategy를 구성합니다. 다음은 어떻게 A를 구성하는지 보겠습니다. 먼저 Factory 대상을 구성합니다.
public Factory(long nowMillis, Request request, Response cacheResponse) {
... ...
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
나중에 캐시 응답이 비어 있지 않을 때 이 응답의 헤더 정보를 주로 얻습니다. Date, Expires Last-Modified ETag 등 정보는 캐시 만료 여부와 캐시 검증을 판단하는 데 사용됩니다.
B, 캐시 정책 가져오기
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;
}
private CacheStrategy getCandidate() {
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
... ....
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);
}
getCandidate는 캐시 정책의 핵심 두 부분입니다. 1) 캐시를 사용하지 않고 return new Cache Strategy(request,null)를 사용합니다.2) 캐시 사용
3, 우리는 다시 CacheInterceptor의 intercept 방법으로 캐시 정책을 얻는다. CacheStrategy를 통해 일련의 판단을 한다. 1) CacheStrategy의 네트워크 Response와 cacheResponse가 비어 있으면 Cache-Conrol이 only-if-cache임을 설명하고 캐시를 사용한다. 2) 네트워크 Response가 비어 있으면 캐시가 유효하다는 것을 설명하고 cacheResponse 3)캐시가 유효하지 않거나 검증이 필요하다면,다음 차단기를 호출하여 네트워크에서 직접 가져옵니다.네트워크에서 응답을 받은 후 처리해야 할 두 가지 상황이 있을 것이다
이로써 캐시 차단기는 캐시 차단기 안에 많은if에 대한 판단을 분석했는데 이것은 모두 Http 캐시 메커니즘에 따라 이루어진 것이다.캐시 차단기를 명확하게 이해하려면 반드시 Http 캐시 메커니즘이 추천하는 읽을거리: 영어가 좋다면: RFC2616 <> 제7장 빠른 이해는 tp 캐시 소개를 참고하십시오.
넷째, 연결 차단기 ConnectInterceptor
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.