OkHttp源码解析(二)

五大拦截器

一、重定向与重试拦截器

复制代码
catch (IOException e) {
    // todo 请求发出去了,但是和服务器通信失败了。(socket 流正在读/写数据时断开连接)
    // HTTP/2 才会抛出 ConnectionShutdownException,所以对于 HTTP/1,requestSendStarted 一定是 true
    boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
    if (!recover(e, streamAllocation, requestSendStarted, request)) {
        throw e;
    }
    releaseConnection = false;
    continue;
}

两个异常都是根据 recover 方法判断是否能够进行重试,如果返回 true ,则表示允许重试。

复制代码
private boolean recover(IOException e,
                        StreamAllocation streamAllocation,
                        boolean requestSendStarted,
                        Request userRequest) {
    streamAllocation.streamFailed(e);

    // todo 1. 如果在 OkHttpClient 配置中关闭了重试(默认允许),则失败后不再重试
    if (!client.retryOnConnectionFailure()) return false;

    // todo 2. 如果是 RouteException 不用看这个条件;
    // 如果是 IOException,且请求体不可重复,则不能重试
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) {
        return false;
    }

    // todo 3. 判断异常是否属于可重试的异常
    if (!isRecoverable(e, requestSendStarted)) return false;

    // todo 4. 是否还有可用的路由路径
    if (!streamAllocation.hasMoreRoutes()) return false;

    // 失败恢复时,使用相同的路由选择器并建立新连接
    return true;
}


1、协议异常,如果是那么直接判定不能重试;(你的请求或者服务器的响应本身就存在问题,没有按照http协议来 定义数据,再重试也没用)
2、超时异常,可能由于网络波动造成了Socket连接的超时,可以使用不同路线重试。
3、SSL证书异常/SSL****验证失败异常,前者是证书验证失败,后者可能就是压根就没证书,或者证书数据不正确, 那还怎么重试?
经过了异常的判定之后,如果仍然允许进行重试,就会再检查当前有没有可用路由路线来进行连接。简单来说,比 如 DNS 对域名解析后可能会返回多个 IP,在一个IP失败后,尝试另一个IP进行重试。

复制代码
private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null ? connection.route() : null;

    int responseCode = userResponse.code();
    final String method = userResponse.request().method();

    switch (responseCode) {
        // 407 代理认证
        case HTTP_PROXY_AUTH: {
            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);
        }

        // 401 服务器认证
        case HTTP_UNAUTHORIZED:
            return client.authenticator().authenticate(route, userResponse);

        // 308/307 保持方法
        case HTTP_PERM_REDIRECT:
        case HTTP_TEMP_REDIRECT: {
            if (!method.equals("GET") && !method.equals("HEAD")) return null;
            // fall-through 到重定向处理
        }

        // 300/301/302/303 各类重定向
        case HTTP_MULT_CHOICE:
        case HTTP_MOVED_PERM:
        case HTTP_MOVED_TEMP:
        case HTTP_SEE_OTHER: {
            if (!client.followRedirects()) return null;

            String location = userResponse.header("Location");
            if (location == null) return null;

            HttpUrl url = userResponse.request().url().resolve(location);
            if (url == null) return null;

            // 跨 Scheme 的重定向是否允许
            boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
            if (!sameScheme && !client.followSslRedirects()) return null;

            Request.Builder requestBuilder = userResponse.request().newBuilder();

            // 是否保留请求体
            if (HttpMethod.permitsRequestBody(method)) {
                final boolean maintainBody = HttpMethod.redirectsWithBody(method);
                if (HttpMethod.redirectsToGet(method)) {
                    requestBuilder.method("GET", null);
                } else {
                    RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
                    requestBuilder.method(method, requestBody);
                }
                if (!maintainBody) {
                    requestBuilder.removeHeader("Transfer-Encoding");
                    requestBuilder.removeHeader("Content-Length");
                    requestBuilder.removeHeader("Content-Type");
                }
            }

            // 跨主机时移除认证头
            if (!sameConnection(userResponse, url)) {
                requestBuilder.removeHeader("Authorization");
            }

            return requestBuilder.url(url).build();
        }

        // 408 客户端超时
        case HTTP_CLIENT_TIMEOUT: {
            if (!client.retryOnConnectionFailure()) return null;
            if (userResponse.request().body() instanceof UnrepeatableRequestBody) return null;

            // 如果前一个响应也是 408,则不再重试
            if (userResponse.priorResponse() != null
                    && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
                return null;
            }

            // Respect Retry-After
            if (retryAfter(userResponse, 0) > 0) return null;

            return userResponse.request();
        }

        // 503 服务不可用
        case HTTP_UNAVAILABLE: {
            if (userResponse.priorResponse() != null
                    && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
                return null;
            }
            if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
                return userResponse.request();
            }
            return null;
        }

        default:
            return null;
    }
}

private boolean sameConnection(Response response, HttpUrl url) {
    HttpUrl priorUrl = response.request().url();
    return priorUrl.host().equals(url.host())
            && priorUrl.port() == url.port()
            && priorUrl.scheme().equals(url.scheme());
}

private int retryAfter(Response response, int defaultDelaySeconds) {
    String header = response.header("Retry-After");
    if (header == null) return defaultDelaySeconds;
    // 仅支持秒的简单实现;非法值按很大值处理表示不应立刻重试
    try {
        return Integer.parseInt(header.trim());
    } catch (NumberFormatException ignore) {
        return Integer.MAX_VALUE;
    }
}

二、桥接拦截器

BridgeInterceptor ,连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,比如设 置请求内容长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作。这个拦截器相对比较简单。
补全请求头:

三、缓存拦截器


3、交给下一个责任链继续处理
4、后续工作,返回304则用缓存的响应;否则使用网络响应并缓存本次响应(只缓存Get请求的响应)
缓存拦截器的工作说起来比较简单,但是具体的实现,需要处理的内容很多。在缓存拦截器中判断是否可以使用缓 存,或是请求服务器都是通过 CacheStrategy 判断。

缓存策略

缓存检测

四、连接拦截器

五、请求服务器拦截器

六、OkHttp****总结

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui