详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

本文介绍Retrofit拦截器(Interceptor)的使用方法及相关注意事项。如果本文对您有所帮助,烦请点亮小红心~

首先看一下Interceptor源码:

/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();
  }
}

先看一下api描述,翻译过来其实就是可以通过拦截器拦截即将发出的请求及对响应结果做相应处理,典型的处理方式是修改header。其实我们可能不仅要处理header,有时也需要添加统一参数,都可以在拦截器内部完成。

看一下Interceptor接口,只有intercept(Chain chain)方法,其返回值是Response,顾名思义,是响应数据,我们要做的也就是重写该方法以达到我们的目的。intercept(Chain chain)方法中有个Chain参数,Chain是Interceptor接口内部中定义的另一个接口,我们暂且不管Retrofit内部是如何实现该接口的(这部分内容将会在新的文章中统一讲解),现在只需要知道调用其request()方法可以拿到Request,调用其proceed(Request request)方法可以得到相应数据即可。

到此为止,Interceptor基本用法已经知晓,下面上示例代码:

public class CommonInterceptor implements Interceptor {

  private static Map<String, String> commonParams;

  public synchronized static void setCommonParam(Map<String, String> commonParams) {
    if (commonParams != null) {
      if (CommonInterceptor.commonParams != null) {
        CommonInterceptor.commonParams.clear();
      } else {
        CommonInterceptor.commonParams = new HashMap<>();
      }
      for (String paramKey : commonParams.keySet()) {
        CommonInterceptor.commonParams.put(paramKey, commonParams.get(paramKey));
      }
    }
  }

  public synchronized static void updateOrInsertCommonParam(@NonNull String paramKey, @NonNull String paramValue) {
    if (commonParams == null) {
      commonParams = new HashMap<>();
    }
    commonParams.put(paramKey, paramValue);
  }

  @Override
  public synchronized Response intercept(Chain chain) throws IOException {
    Request request = rebuildRequest(chain.request());
    Response response = chain.proceed(request);
    // 输出返回结果
    try {
      Charset charset;
      charset = Charset.forName("UTF-8");
      ResponseBody responseBody = response.peekBody(Long.MAX_VALUE);
      Reader jsonReader = new InputStreamReader(responseBody.byteStream(), charset);
      BufferedReader reader = new BufferedReader(jsonReader);
      StringBuilder sbJson = new StringBuilder();
      String line = reader.readLine();
      do {
        sbJson.append(line);
        line = reader.readLine();
      } while (line != null);
      LogUtil.e("response: " + sbJson.toString());
    } catch (Exception e) {
      e.printStackTrace();
      LogUtil.e(e.getMessage(), e);
    }
//    saveCookies(response, request.url().toString());
    return response;
  }

  public static byte[] toByteArray(RequestBody body) throws IOException {
    Buffer buffer = new Buffer();
    body.writeTo(buffer);
    InputStream inputStream = buffer.inputStream();
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    byte[] bufferWrite = new byte[4096];
    int n;
    while (-1 != (n = inputStream.read(bufferWrite))) {
      output.write(bufferWrite, 0, n);
    }
    return output.toByteArray();
  }

  private Request rebuildRequest(Request request) throws IOException {
    Request newRequest;
    if ("POST".equals(request.method())) {
      newRequest = rebuildPostRequest(request);
    } else if ("GET".equals(request.method())) {
      newRequest = rebuildGetRequest(request);
    } else {
      newRequest = request;
    }
    LogUtil.e("requestUrl: " + newRequest.url().toString());
    return newRequest;
  }

  /**
   * 对post请求添加统一参数
   */
  private Request rebuildPostRequest(Request request) {
//    if (commonParams == null || commonParams.size() == 0) {
//      return request;
//    }
    Map<String, String> signParams = new HashMap<>(); // 假设你的项目需要对参数进行签名
    RequestBody originalRequestBody = request.body();
    assert originalRequestBody != null;
    RequestBody newRequestBody;
    if (originalRequestBody instanceof FormBody) { // 传统表单
      FormBody.Builder builder = new FormBody.Builder();
      FormBody requestBody = (FormBody) request.body();
      int fieldSize = requestBody == null ? 0 : requestBody.size();
      for (int i = 0; i < fieldSize; i++) {
        builder.add(requestBody.name(i), requestBody.value(i));
        signParams.put(requestBody.name(i), requestBody.value(i));
      }
      if (commonParams != null && commonParams.size() > 0) {
        signParams.putAll(commonParams);
        for (String paramKey : commonParams.keySet()) {
          builder.add(paramKey, commonParams.get(paramKey));
        }
      }
      // ToDo 此处可对参数做签名处理 signParams
      /**
       * String sign = SignUtil.sign(signParams);
       * builder.add("sign", sign);
       */
      newRequestBody = builder.build();
    } else if (originalRequestBody instanceof MultipartBody) { // 文件
      MultipartBody requestBody = (MultipartBody) request.body();
      MultipartBody.Builder multipartBodybuilder = new MultipartBody.Builder();
      if (requestBody != null) {
        for (int i = 0; i < requestBody.size(); i++) {
          MultipartBody.Part part = requestBody.part(i);
          multipartBodybuilder.addPart(part);

          /*
           上传文件时,请求方法接收的参数类型为RequestBody或MultipartBody.Part参见ApiService文件中uploadFile方法
           RequestBody作为普通参数载体,封装了普通参数的value; MultipartBody.Part即可作为普通参数载体也可作为文件参数载体
           当RequestBody作为参数传入时,框架内部仍然会做相关处理,进一步封装成MultipartBody.Part,因此在拦截器内部,
           拦截的参数都是MultipartBody.Part类型
           */

          /*
           1.若MultipartBody.Part作为文件参数载体传入,则构造MultipartBody.Part实例时,
           需使用MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)方法,
           其中name参数可作为key使用(因为你可能一次上传多个文件,服务端可以此作为区分)且不能为null,
           body参数封装了包括MimeType在内的文件信息,其实例创建方法为RequestBody.create(final @Nullable MediaType contentType, final File file)
           MediaType获取方式如下:
           String fileType = FileUtil.getMimeType(file.getAbsolutePath());
           MediaType mediaType = MediaType.parse(fileType);

           2.若MultipartBody.Part作为普通参数载体,建议使用MultipartBody.Part.createFormData(String name, String value)方法创建Part实例
            name可作为key使用,name不能为null,通过这种方式创建的实例,其RequestBody属性的MediaType为null;当然也可以使用其他方法创建
           */

          /*
           提取非文件参数时,以RequestBody的MediaType为判断依据.
           此处提取方式简单暴力。默认part实例的RequestBody成员变量的MediaType为null时,part为非文件参数
           前提是:
           a.构造RequestBody实例参数时,将MediaType设置为null
           b.构造MultipartBody.Part实例参数时,推荐使用MultipartBody.Part.createFormData(String name, String value)方法,或使用以下方法
            b1.MultipartBody.Part.create(RequestBody body)
            b2.MultipartBody.Part.create(@Nullable Headers headers, RequestBody body)
            若使用方法b1或b2,则要求

           备注:
           您也可根据需求修改RequestBody的MediaType,但尽量保持外部传入参数的MediaType与拦截器内部添加参数的MediaType一致,方便统一处理
           */

          MediaType mediaType = part.body().contentType();
          if (mediaType == null) {
            String normalParamKey;
            String normalParamValue;
            try {
              normalParamValue = getParamContent(requestBody.part(i).body());
              Headers headers = part.headers();
              if (!TextUtils.isEmpty(normalParamValue) && headers != null) {
                for (String name : headers.names()) {
                  String headerContent = headers.get(name);
                  if (!TextUtils.isEmpty(headerContent)) {
                    String[] normalParamKeyContainer = headerContent.split("name=\"");
                    if (normalParamKeyContainer.length == 2) {
                      normalParamKey = normalParamKeyContainer[1].split("\"")[0];
                      signParams.put(normalParamKey, normalParamValue);
                      break;
                    }
                  }
                }
              }
            } catch (Exception e) {
              e.printStackTrace();
            }
          }
        }
      }
      if (commonParams != null && commonParams.size() > 0) {
        signParams.putAll(commonParams);
        for (String paramKey : commonParams.keySet()) {
          // 两种方式添加公共参数
          // method 1
          multipartBodybuilder.addFormDataPart(paramKey, commonParams.get(paramKey));
          // method 2
//          MultipartBody.Part part = MultipartBody.Part.createFormData(paramKey, commonParams.get(paramKey));
//          multipartBodybuilder.addPart(part);
        }
      }
      // ToDo 此处可对参数做签名处理 signParams
      /**
       * String sign = SignUtil.sign(signParams);
       * multipartBodybuilder.addFormDataPart("sign", sign);
       */
      newRequestBody = multipartBodybuilder.build();
    } else {
      try {
        JSONObject jsonObject;
        if (originalRequestBody.contentLength() == 0) {
          jsonObject = new JSONObject();
        } else {
          jsonObject = new JSONObject(getParamContent(originalRequestBody));
        }
        if (commonParams != null && commonParams.size() > 0) {
          for (String commonParamKey : commonParams.keySet()) {
            jsonObject.put(commonParamKey, commonParams.get(commonParamKey));
          }
        }
        // ToDo 此处可对参数做签名处理
        /**
         * String sign = SignUtil.sign(signParams);
         * jsonObject.put("sign", sign);
         */
        newRequestBody = RequestBody.create(originalRequestBody.contentType(), jsonObject.toString());
        LogUtil.e(getParamContent(newRequestBody));

      } catch (Exception e) {
        newRequestBody = originalRequestBody;
        e.printStackTrace();
      }
    }
//    可根据需求添加或修改header,此处制作示意
//    return request.newBuilder()
//        .addHeader("header1", "header1")
//        .addHeader("header2", "header2")
//        .method(request.method(), newRequestBody)
//        .build();
    return request.newBuilder().method(request.method(), newRequestBody).build();
  }

  /**
   * 获取常规post请求参数
   */
  private String getParamContent(RequestBody body) throws IOException {
    Buffer buffer = new Buffer();
    body.writeTo(buffer);
    return buffer.readUtf8();
  }

  /**
   * 对get请求做统一参数处理
   */
  private Request rebuildGetRequest(Request request) {
    if (commonParams == null || commonParams.size() == 0) {
      return request;
    }
    String url = request.url().toString();
    int separatorIndex = url.lastIndexOf("?");
    StringBuilder sb = new StringBuilder(url);
    if (separatorIndex == -1) {
      sb.append("?");
    }
    for (String commonParamKey : commonParams.keySet()) {
      sb.append("&").append(commonParamKey).append("=").append(commonParams.get(commonParamKey));
    }
    Request.Builder requestBuilder = request.newBuilder();
    return requestBuilder.url(sb.toString()).build();
  }
}

该拦截器示例代码提供了插入公共参数及对添加header功能(该功能在代码中被注释掉,如需要,放开即可)。对Request的拦截处理在rebuildRequest(Request request) 方法中完成,该方法只处理了GET与POST请求,内部有较为详尽的注释,较为复杂的是文件传输,有些需要注意的事项也做了尽可能完善的说明;对响应数据的处理,代码示例中只做了结果输出处理,仅仅做个示范。

拦截器部分没有过多需要做说明的地方,比较简单,本文的示例可直接使用。如有疑问,欢迎留言。

后续将抽时间,对Retrofit做流程上的简单梳理,了解各个配置及部分细节实现,比如该文中的Chain实例

完整示例: https://github.com/670832188/TestApp

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • OKHttp3(支持Retrofit)的网络数据缓存Interceptor拦截器的实现

    前言:前段时间在开发APP的时候,经常出现由于用户设备环境的原因,拿不到从网络端获取的数据,所以在APP端展现的结果总是一个空白的框,这种情况对于用户体验来讲是极其糟糕的,所以,苦思冥想决定对OKHTTP下手(因为我在项目中使用的网络请求框架就是OKHTTP),则 写了这么一个网络数据缓存拦截器. OK,那么我们决定开始写了,我先说一下思路: 思路篇 既然要写的是网络数据缓存拦截器,主要是利用了OKHTTP强大的拦截器功能,那么我们应该对哪些数据进行缓存呢,或者在哪些情况下启用数据进行缓存机制呢

  • 详解Retrofit Interceptor(拦截器) 拦截请求并做相关处理

    本文介绍Retrofit拦截器(Interceptor)的使用方法及相关注意事项.如果本文对您有所帮助,烦请点亮小红心- 首先看一下Interceptor源码: /** * Observes, modifies, and potentially short-circuits requests going out and the corresponding * responses coming back in. Typically interceptors add, remove, or tran

  • Springboot如何利用拦截器拦截请求信息收集到日志详解

    目录 1.需求 2.问题 2.获取 1)导入依赖为了获取客户端类型.操作系统类型.ip.port 2)封装获取body字符串的工具类 3)拦截器类 4)继承 HttpServletRequestWrapper类 5)过滤器类 6)拦截器过滤器配置类 总结 1.需求 最近在工作中遇到的一个需求,将请求中的客户端类型.操作系统类型.ip.port.请求方式.URI以及请求参数值收集到日志中,网上找资料说用拦截器拦截所有请求然后收集信息,于是就开始了操作: 2.问题 试了之后发现当请求方式为POST,

  • 详解springmvc拦截器拦截静态资源

    springmvc拦截器interceptors springmvc拦截器能够对请求的资源路径进行拦截,极大的简化了拦截器的书写.但是,千万千万要注意一点:静态资源的放行. 上代码: <mvc:resources mapping="/resources/**" location="/static/resources" /> <mvc:resources mapping="/static/css/**" location=&quo

  • spring boot使用拦截器修改请求URL域名 换 IP 访问的方法

    目录 Interceptor 介绍 Interceptor 作用 自定义 Interceptor 案例1 :域名换IP访问 案例2: erverWebExchange通过拦截器修改请求url 案例3: 将请求路径中/idea都去掉 案例4: SpringBoot 利用过滤器Filter修改请求url地址 案例5.拦截器: WebMvcConfigurerAdapter拦截器 结语 Interceptor 介绍 拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——

  • 详解Retrofit 动态参数(非固定参数、非必须参数)(Get、Post请求)

    详解Retrofit 动态参数(非固定参数.非必须参数)(Get.Post请求) 关键词:Retrofit 动态参数.非固定参数.非必须参数 有如下场景: 请求数据时: 1. 用户未登录时,不带参数userId: 2. 登录时带上参数userId. 如下接口: @GET("index.php?r=default/homepage") Observable<Response<Exercise>> getDataList(@Query("page"

  • 详解OAuth2 Token 一定要放在请求头中吗

    Token 一定要放在请求头中吗? 答案肯定是否定的,本文将从源码的角度来分享一下 spring security oauth2 的解析过程,及其扩展点的应用场景. Token 解析过程说明 当我们使用 spring security oauth2 时, 一般情况下需要把认证中心申请的 token 放在请求头中请求目标接口,如下图 ① spring security oauth2 通过拦截器获取此 token 完成令牌到当前用户信息(UserDetails)的转换. OAuth2Authenti

  • 实例详解jQuery Mockjax 插件模拟 Ajax 请求

    1. 原理 jquery-mockjax是用于mock 前台ajax向后台请求的返回数据. 原理很简单 在你js代码要发送ajax请求的地方断点一下,然后比较在[引入jquery-mockjax] 和 [没有引入jquery-mockjax]的情况下$.ajax.toString()的值情况. 很明显,引入jquery-mockjax时,这个mock库会对jquery提供的ajax函数做替换.这样就很容易能mock起来. 在实际的开发过程中,前端后台协商好了统一的接口,就各自开始自己的任务了.这

  • 防止SpringMVC拦截器拦截js等静态资源文件的解决方法

    SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下方案进行解决: 方案一.拦截器中增加针对静态资源不进行过滤(涉及spring-mvc.xml) <mvc:resources location="/" mapping="/**/*.js"/> <mvc:resources location="/" mapping=&quo

  • 详解Opentelemetry Collector采集器

    目录 前言 客户端数据上报 OTLP OTLP/HTTP OTLP/gRPC Collector Collector简介 Collector使用 Receiver Processor Exportor Extension Service 个性化的Collector 总结 前言 上个篇章中我们主要介绍了OpenTelemetry的客户端的一些数据生成方式,但是客户端的数据最终还是要发送到服务端来进行统一的采集整合,这样才能看到完整的调用链,metrics等信息.因此在这个篇章中会主要介绍服务端的采

  • 解决Spring boot2.0+配置拦截器拦截静态资源的问题

    第一次遇到这个问题的时候,简直是一脸蒙逼,写了一个拦截器以后,静态资源就不能访问了,到处查找才知道是版本问题 解决办法: 第一步:定义一个类实现 实现WebMvcConfigurer的类中拦截器中添加放行资源处添加静态资源文件路径: @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(sessionInterceptor).addPathPatterns("/&

随机推荐