okhttp 应用之 Interceptors 拦截器,「实践 + 原理」一样都不少

(三)OkHttp网络请求拦截器详解 这个拦截器一般做网络调试用,用户自定义,插在连接拦截器和请求服务拦截器之间。整个OkHttp的网络请求工作都是在这个五大拦截器中完成。他们分别负责不同的工作,如果流水线一样,经过五到工序,最终完成了完整的工作。 阅读详情

前言

参考源码版本 okhttp-3.14.9

okhttp 是什么?一款封装 HTTP 协议的 HTTP 客户端。

拦截器是 okhttp 提供的一个强有力的工具,我们可以在请求前后做监控、对请求/响应进行重写以及失败重试等操作。


一、拦截器

Okhttp 的拦截器采用了责任链模式,具体提供了两种拦截器:

  • 应用拦截器:专注于业务层的逻辑处理,比如 添加日志 ...
  • 网络拦截器:专注于网络层请求的处理,比如 重定向、重试 ...

这是官方提供的图片,你可以参考下:

我们通过实际的例子来看看两种的区别,方便起,实现一个公用拦截器:

class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request request = chain.request();

    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));

    Response response = chain.proceed(request);

    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}

从名字可以看出,这是一个记录日志的拦截器。

官方提供的测试网址,www.publicobject.com/helloworld.…,当我们访问该网址的时候,会重定向到 publicobject.com/helloworld.… 地址。

现在,我们通过 okhttp 请求来看看两种拦截器的差别。

1. 应用拦截器

这里通过 addInterceptor(...) 添加应用拦截器:

    /**
     * 应用层拦截器
     */
    @Test
    public void testApplicationInterceptors() throws IOException {
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new LoggingInterceptor())
                .build();

        Request request = new Request.Builder()
                .url("http://www.publicobject.com/helloworld.txt")
                .header("User-Agent", "OkHttp Example")
                .build();

        Response response = client.newCall(request).execute();
        Objects.requireNonNull(response.body()).close();
    }

我们看看处理结果:

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

从结果可以看出,控制台主要输出了我们请求前和请求后的日志信息。

2. 网络拦截器

通过 addNetworkInterceptor(...) 添加网络拦截器:

    /**
     * 网络层拦截器
     */
    @Test
    public void testNetworkInterceptors() throws IOException {
        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new LoggingInterceptor())
                .build();

        Request request = new Request.Builder()
                .url("http://www.publicobject.com/helloworld.txt")
                .header("User-Agent", "OkHttp Example")
                .build();

        Response response = client.newCall(request).execute();
        Objects.requireNonNull(response.body()).close();
    }

再来看看处理结果:

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

你应该也看出差别了,这次控制台输出了两份 请求 + 响应 信息:

另外,输出里还包含了更多的网络层信息,比如 okhttp 添加的头信息:Accept-Encoding: gzip

二、选择?

两种拦截器都有各自的应用场景,我们该如何选择?

1. 应用拦截器

  • 不需要考虑重定向和重试等中间响应。
  • 仅调用一次,即使 http 响应是从缓存中获取的结果。
  • 主要关注应用程序的原始意图,不关心 okhttp 注入的头,如 If-None-Match ...
  • 允许短路操作,即 不调用 Chain.proceed()。
  • 允许重试并多次调用 Chain.proceed()
  • 可以使用 withConnectTimeout, withReadTimeout, withWriteTimeout 来调整 Call 超时时间。

2. 网络拦截器

  • 能够操作中间处理过程,如重定向和重试。
  • 不关注 cache 层拦截的短路操作
  • 关注网络层数据传输
  • 执行 Connection 请求

3. 重写请求

有了拦截器,我们可以对 http 请求头的参数新增、删除和覆盖等操作。

如果你觉得请求体过大,数据传输耗时较高的话,你可以对请求体进行压缩:


final class GzipRequestInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request originalRequest = chain.request();
    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
      return chain.proceed(originalRequest);
    }

    Request compressedRequest = originalRequest.newBuilder()
        .header("Content-Encoding", "gzip")
        .method(originalRequest.method(), gzip(originalRequest.body()))
        .build();
    return chain.proceed(compressedRequest);
  }

  private RequestBody gzip(final RequestBody body) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return body.contentType();
      }

      @Override public long contentLength() {
        return -1; // We don't know the compressed length in advance!
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}

案例中,我们覆盖了请求头参数 Content-Encoding:gzip,也就是告诉服务器,这个一个压缩请求体,你需要用相同的方式来解压数据。

4. 重写响应

这通常比重写请求头更危险,因为它可能违反web服务器的期望!

当然,如果你处于一个棘手的情况,并准备处理结果,重写响应头是解决问题的一种强大方法。

例如,你可以修复服务器错误配置的 Cache-Control 响应头,以启用更好的响应缓存:

/** Dangerous interceptor that rewrites the server's cache-control header. */
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
        .header("Cache-Control", "max-age=60")
        .build();
  }
};

通常,当它补充了web服务器上的相应修复时,这种方法工作得最好!

三、原理

老规矩,我们还是回归源码看看底层实现细节。

再回归下我们的测试案例:

    /**
     * 网络层拦截器
     */
    @Test
    public void testNetworkInterceptors() throws IOException {
        OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new LoggingInterceptor())
                .build();

        Request request = new Request.Builder()
                .url("http://www.publicobject.com/helloworld.txt")
                .header("User-Agent", "OkHttp Example")
                .build();
        // 执行 okhttp 请求
        Response response = client.newCall(request).execute();
        Objects.requireNonNull(response.body()).close();
    }

1. 提交请求:

入口 okhttp3.RealCall#execute:

可以看到,getResponseWithInterceptorChain 真正应用拦截器并执行 http 请求。

2. 拦截器链

okhttp3.RealCall#getResponseWithInterceptorChain:

看到拦截器的装配了!!!离真相不远了。

该方法主要做了两件大事:

  • 为请求组装拦截器
  • 定义拦截器链 chain,并通过 chain.proceed(originalRequest) 执行

到这里,其实已经可以解释很多现象了,比如应用拦截器与网络拦截器的核心区别是啥?为什么都是 Interceptor 的实现类,效果却有些不同?

你应该也发现本质原因了,主要区别在于拦截器装配的顺序不一样。拦截器处理顺序:

  • 处理请求时:顺序出场
  • 处理响应时,与处理请求顺序相反

在添加网络拦截器之前,添加了几个固定拦截器,比如 重试、桥接、缓存等拦截器。因此,网络层拦截器展示的效果与应用层拦截器不同,主要是由这几个拦截器在中间发挥作用。

我们再来回顾下这张图,你应该理解的更加深刻了:

值得注意的是,最后一个添加的拦截器是 CallServerInterceptor,是真正执行 http 请求的拦截器。

换句话说,okhttp 将所有操作包括 执行 http 调用都封装为拦截器,然后通过拦截器链串联起来执行。

3. 执行请求

okhttp3.internal.http.RealInterceptorChain#proceed:

前面提到,拦截器是一个 List 集合,那么如何决定当前使用哪一个拦截器?

答案是:index 下标

原理是这样的:

  • 首先,RealInterceptorChain#proceed 是将所有拦截器串联起来。
  • 另外,我们在每个拦截器里面都会通过 chain.proceed 去处理真正的逻辑(本质是调用下一个拦截器)
  • 而 chain.proceed 进入的就是 RealInterceptorChain#proceed 调用,也就是回到了这个方法,不过 index + 1,即 执行下一个拦截器。
  • 如此往复,直到最后一个拦截器,便执行真正的 http 请求。
  • 拦截器处理响应请求时,则是按照与处理请求相反的顺序执行。

进入我们自定义的拦截器:

至此,okhttp 请求处理流程梳理完毕!!!可以发现,okhttp 的核心链路是围绕着拦截器进行设计,我们可以实现任何自定义拦截器并添加至其中,非常方便。

总结

本文带你一起探讨了 okhttp 拦截器的使用方法,及其底层处理原理,相信你也有了自己的想法。

拦截器的应用十分广泛,常见的还用 Spring 拦截器、Mybatis 拦截器以及 Feign 拦截器,相信你在开发过程中也遇到了很多。

你可以进一步探讨拦截器,多对比几款实现,看能否抽象出一款拦截器的通用理论?

# Okhttp解析—Interceptor详解 Okhttp解析—Interceptor详解 Interceptor可以说是okhttp的精髓之一,Okhttp重写请求/响应、重试、缓存响应等操作,基本都是在各个Interceptor中完成的,上篇文章分析Okhttp运行流程时只是简单带过,那么这篇文章就来详细分析下Interceptor以及拦截器链机制。 一、Interceptor以及InterceptorChain /** * Ob... 阅读详情

相关推荐

OkHttpClient常见方法和使用

OkHttpClient是一个开源的HTTP客户端,用于发送和接收HTTP请求。它是Square公司开发的,用于Android和Java应用程序的网络库。OkHttpClient内置了很多功能,包括连接池、请求和响应拦截器、TLS握手、请求重试等。它易于使用,具有简洁的API,同时还提供了丰富的配置选项,以适应不同的网络需求。使用OkHttpClient,开发人员可以轻松地创建和发送GET、POST、PUT、DELETE等类型的HTTP请求,并处理响应。

架构师之路。。 4594

Okhttp之五种拦截器

是整个责任链中的第一个,是首次接触到Request与最后接收到 Response的,主要功能就是判断是否需要重试与重定向。重试的前提是出现了 RouteException 或IOException。一旦在后续的拦截器执行过程中出现这两个异常,就会通过 recover 方法进行判断是否进行连接重试。重定向发生在重试的判定之后,如不满足重试的条件,还需要进一步调用 followUpRequest 根据Response 的响应码(如果直接请求失败, Response都不存在就会抛出异常)。

追梦的鱼儿 7133

OkHttp 3.x 源码解析之Interceptor 拦截器

Tamic / http://blog.youkuaiyun.com/sk719887916/article/details/74308343 OkHttp拦截器原理解析拦截器Java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。 在

Tamic (大白) 1万+

OkHttp3.9拦截器原理与区别

接上回传送门 上回我们讲到,OkHttp的请求过程中有个非常重要的东西-“拦截器”,而且拦截器又分为interceptors和networkInterceptors两种,那它们具体有何区别呢?又要怎么来使用?现在来一探究竟 拦截器工作原理 在弄清楚区别之前,要先知道他们工作的原理,还是来到RealCall.execute方法里面的getResponseWithInterceptorChain...

梦里梦到醒不来的梦 1055

OkHttp拦截器机制解析与实战应用

责任链模式是软件设计中处理请求传递的经典模式,通过将处理对象连成链条并依次传递请求,实现解耦和灵活扩展。OkHttp作为Android主流网络库,其拦截器机制基于责任链模式构建,支持请求/响应的双向处理。开发者可以通过自定义拦截器实现日志记录、性能监控、数据加密等通用功能,这种设计显著提升了代码复用率和可维护性。在电商、社交等高频网络请求场景中,合理使用OkHttp拦截器能有效降低40%以上的网络层代码量。本文以日志拦截器和耗时监控为例,详解如何通过addInterceptor()方法实现网络请求的全链路管

weixin_34268310的博客 331

“深入交流“系列:Okhttp(二)拦截器的实现

Okhttp拦截器详解 Okhttp拦截器介绍 概念:拦截器是Okhttp中提供的一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。我们先来了解下Okhttp中的系统拦截器: RetryAndFollowUpInterceptor:负责请求失败的时候实现重试重定向功能。 BridgeInterceptor:将用户构造的请求转换为向服务器发送的请求,将服务器返回的响应转换为对用户友好的响应。 CacheInterceptor:读取缓存、更新缓存。 ConnectInterceptor:

Aerfa789的博客 740

Okhttp-wiki 之 Interceptors 拦截器

Okhttp-wiki 之 Interceptors 拦截器

idaretobe的专栏 281

OkHttp与Retrofit在Android开发中的核心原理与面试要点

HTTP客户端框架是移动开发中处理网络请求的核心组件,OkHttp作为底层通信引擎通过拦截器链机制实现请求处理流程的可扩展性,其连接池复用策略能显著降低网络延迟。Retrofit则基于动态代理模式,将REST API抽象为Java接口,配合RxJava或协程实现异步编程。这对黄金组合广泛应用于Android项目,涉及HTTP协议、设计模式和线程模型等核心技术点。在面试中,OkHttp的缓存机制、拦截器执行顺序以及Retrofit的适配器原理成为高频考点,深入理解这些机制能体现开发者对网络层架构的设计能力。

weixin_33827590的博客 400

Interceptors拦截器——OkHttp3详细使用教程

这篇文章是从官网https://github.com/square/okhttp/wiki/Interceptors翻译来的,但是我发现官网的演示代码有一个问题(见第一段代码注释),所以写转载这么一篇,便于复习,也顺便改正代码在OkHttp中Interceptors拦截器是一种强大的机制,可以监视,重写和重试Call请求。下面是一个简单的拦截器,它记录发出的请求和返回的响应。class Loggin

薛瑄的博客 1万+

MyBatis源码剖析 - MyBatis 插件之拦截器(Interceptor)实现原理

在上一篇文章:MyBatis 教程 - MyBatis插件(Plugins)开发 中已经介绍了如何去开发一个MyBatis 插件,本文将结合MyBatis 源码来揭秘MyBatis Plugins内部实现原理。Mybatis3 插件采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)。本文分析使用的MyBatis 源代码版本为3.

Ricky 2万+

不是吧!做了3年Android还没看过OkHttp源码?好吧,带你彻底理解一波!

前言 现在Android界的网络请求已经是OkHttp和Retrofit的天下了,Retrofit本质上也是将请求委托给了OkHttp,所以我们如果想要能够更加全面的使用和掌握OkHttp,了解其源码是必不可少的。 如果对Http还有不了解的小伙伴,可以一下这篇文章,带你全面掌握Http协议 面试官的这份HTTP灵魂追问你Hold住吗? 作者:Mlx 链接:https://juejin.im/post/6881436122950402056 OK,出发~ 简单使用 同步方式和异步方式殊途同归,而且异步

chuhe1989的博客 975

OkHttp初识拦截器

OkHttp拦截器

weixin_42814000的博客 1410

框架和流程——OkHttp 源码详解(一)

前言 OkHttp应该是目前Android平台上使用最为广泛的开源网络库了,Android 在6.0之后也将内部的HttpUrlConnection的默认实现替换成了OkHttp。 网上很多分析OkHttp的,都是在总体流程上,没有那么的细致,甚至有的同学看完了文章,认为OkHttp没有DNS解析。我说有,他说没有,我说有,他说没有,我说有,他说没有,我就站起来了,很快啊,年轻人, 一、鸟瞰OkHttp 下面的代码是一个很简单的例子,一步一步分析,起内部的工作原理 OkHttpClient client

薛瑄的博客 2298

OkHttp源码解析(上)

导语 学过Android开发的同学都知道,我们使用网络请求获取服务器的数据时,通常使用的是封装好的Retrofit框架,这个框架很好的帮助我们对网络的发起,以及返回的数据进行操作,我们使用起来十分方便,对于Retrofit来说,我们仅仅看到了它的表面,如何正确使用等,其内部还是要借助OkHtttp来请求网络的,Retrofit只是对OkHttp进行了再次的封装,而且Retrofit不具备网络请求功能,只是在OkHtttp的外表又套了一层,对返回的数据支持RxJava转换和Gson解析。真正起到网络请求的还是

伟子时代的博客 2721

深度学习模型轻量化方法之【量化】

量化虽然有着硬件友好和功效提升的特点,但它也引入了模型性能的折衷。因此,设计有效的量化策略需要在模型大小、性能以及运算速度之间做出平衡。随着深度学习算法和硬件加速技术的快速发展,量化方法在使深度网络轻量化和提高部署效率方面持续发挥着重要作用。

m0_73916791的博客 2850

保姆级教程:用NVIDIA Jetson AGX Xavier和GMSL2相机搭建8路车载视觉原型系统

本文提供了一份详细的教程,指导如何使用NVIDIA Jetson AGX Xavier开发板和GMSL2相机搭建8路车载视觉原型系统。内容涵盖硬件选型、系统架构设计、软件环境配置、多路视频采集实现以及性能优化与问题排查,帮助开发者快速掌握GMSL2技术在车载视觉系统中的应用。

weixin_42525005的博客 295

OkHttp使用方法

  一.概述 Okhttp集成有多种,一种就是直接下载它的jar包但是并不推荐,一种是Maven方式去构建,这种在java程序里用的会比较多 <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <...

gxflh的博客 898

Ubuntu系统下搭建QtCreator开发环境详细过程(Qt简介;Linux下安装QtCreator;Ubuntu安装后闪退蓝屏的解决方法)

Qt 是一个跨平台的 C++开发库。主要用来开发图形用户界面 (Graphical User Interface,简称 GUI) 程序。Qt 支持的操作系统有很多,例如通用操作系统 Windows、Linux、Unix、智能手机系统Android、ios、WinPhone, 嵌入式系统 QNX、VxWorks 等。

分享学习成长历程,Winter is coming! 8594

OKHttp 执行拦截器前

在同步和异步方法中都调用 getResponseWithInterceptorChain(): Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors ...

闪电人 652

QMT Python API 实战指南:从安装到策略开发的完整流程

本文提供了一份详尽的QMT Python API实战指南,涵盖从环境配置到策略开发的完整流程。文章深入解析了xtdata行情模块与xttrader交易模块的核心架构与协作方式,并通过一个双均线策略实例,手把手教你如何获取数据、计算信号并执行交易,是量化交易开发者快速上手QMT Python接口文档的实用教程。

butter的博客 1416

详细解读文字识别工具———Tesseract-OCR

   在尝试破解12306验证码的时候,提前了解了一下应对各种验证码的解决方案,这里主要是学习到了面对文字图片验证码,例如亚马逊、豆瓣等平台的登录时会遇到的情况,效果还不错。 一、工具介绍 ​    Tesseract-OCR 是一款由HP实验室开发由Google维护的开源OCR(Optical Character Recognition...

别看我只是一只羊 2万+

OK Http-Interceptors

Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. Here's a simple interceptor that logs the outgoing request and the incoming response. class LoggingInterceptor im

cxcbupt 604

YOLOv5入门实践(1)——手把手带你环境配置搭建

YOLOv5入门实践第一步,手把手带你配置环境

路人贾的博客 2万+
上一篇: 你好,SLF4J
下一篇: 6年拉力经验,学xiP8架构师的7+1+1落地项目,跳槽阿里
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值