Explain in detail that Retrofit Interceptor of interceptor intercepts requests and processes them

  • 2021-09-05 00:53:14
  • OfStack

This paper introduces the use of Retrofit interceptor (Interceptor) and related matters needing attention. If this article is helpful to you, please light up the little red heart ~

First look at the Interceptor source code under 1:


/**
* 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();
  }
}

First, look at the description of api. In fact, the interceptor can intercept the upcoming request and deal with the response results accordingly. The typical processing method is to modify header. In fact, we may not only have to deal with header, but also need to add unified 1 parameters sometimes, which can be done inside the interceptor.

Looking at the Interceptor interface, there is only the intercept (Chain chain) method, and its return value is Response, which, as its name implies, is response data. All we have to do is override this method to achieve our goal. There is an Chain parameter in intercept (Chain chain) method, and Chain is another interface defined in Interceptor interface. For the time being, we don't care how to implement this interface in Retrofit (this part will be explained in the new article). Now we only need to know that calling its request () method can get Request, and calling its proceed (Request request) method can get corresponding data.

At this point, the basic usage of Interceptor is known, and the following example code:


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请求添加统1参数
   */
  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作为参数传入时,框架内部仍然会做相关处理,进1步封装成MultipartBody.Part,因此在拦截器内部,
           拦截的参数都是MultipartBody.Part类型
           */

          /*
           1.若MultipartBody.Part作为文件参数载体传入,则构造MultipartBody.Part实例时,
           需使用MultipartBody.Part.createFormData(String name, @Nullable String filename, RequestBody body)方法,
           其中name参数可作为key使用(因为你可能1次上传多个文件,服务端可以此作为区分)且不能为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与拦截器内部添加参数的MediaType1致,方便统1处理
           */

          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请求做统1参数处理
   */
  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();
  }
}

The interceptor sample code provides the ability to insert common parameters and add header (this ability is commented out in the code, and can be released if necessary). The interception processing of Request is completed in rebuildRequest (ES40request) method, which only processes GET and POST requests, with detailed comments inside, and the more complicated one is file transfer, and some matters needing attention are explained as well as possible; For the processing of response data, the code example only does the result output processing, just do a demonstration.

The interceptor section does not need to be explained too much, it is relatively simple, and the examples in this article can be used directly. If you have any questions, please leave a message.

In the future, we will take time to sort out the process of Retrofit, and understand each configuration and some details, such as the Chain example in this paper

Complete example: https://github.com/670832188/TestApp


Related articles: