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****总结

相关推荐
allk553 小时前
OkHttp源码解析(一)
android·okhttp
aFakeProgramer3 小时前
拆分PDF.html 办公小工具
okhttp
2501_915909066 小时前
原生 iOS 开发全流程实战,Swift 技术栈、工程结构、自动化上传与上架发布指南
android·ios·小程序·uni-app·自动化·iphone·swift
2501_915909066 小时前
苹果软件混淆与 iOS 代码加固趋势,IPA 加密、应用防反编译与无源码保护的工程化演进
android·ios·小程序·https·uni-app·iphone·webview
2501_916007476 小时前
苹果软件混淆与 iOS 应用加固实录,从被逆向到 IPA 文件防反编译与无源码混淆解决方案
android·ios·小程序·https·uni-app·iphone·webview
介一安全6 小时前
【Frida Android】基础篇6:Java层Hook基础——创建类实例、方法重载、搜索运行时实例
android·java·网络安全·逆向·安全性测试·frida
沐怡旸9 小时前
【底层机制】【Android】深入理解UI体系与绘制机制
android·面试
啊森要自信9 小时前
【GUI自动化测试】YAML 配置文件应用:从语法解析到 Python 读写
android·python·缓存·pytest·pip·dash
下位子11 小时前
『AI 编程』用 Codex 开发识字小帮手应用
android·openai·ai编程