拦截器Interceptor

在这里插入图片描述
在这里插入图片描述

参考:

OkHttp 拦截器的一些骚操作

Okhttp-wiki 之 Interceptors 拦截器

一,OkHttp 拦截器介绍(译自官方文档)

dingProg /NetworkCaptureSelf :app上监听网络日志

简介

OkHttp 中的拦截器分为 Application Interceptor(应用拦截器) 和 NetWork Interceptor(网络拦截器)两种,
下面以 LoggingInterceptor 为例来展示这两种注册方式的区别:

• Application Interceptor(应用拦截器)

通过调用 OkHttpClient.Builder 的 addInterceptor() 方法来注册应用拦截器

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();
response.body().close();

上段代码中的URL http://www.publicobject.com/helloworld.txt
被重定向到了 https://publicobject.com/helloworld.txt,
OkHttp会自动跟随此重定向。此时的应用拦截器会被调用一次,
并且返回的 chain.proceed() 响应是重定向后的响应。

• Network Interceptor(网络拦截器)

通过调用 OkHttpClient.Builder 的 addNetworkInterceptor() 方法来注册网络拦截器

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();
response.body().close();

当我们运行此代码,拦截器会运行两次,
一次用于初始请求 http://www.publicobject.com/helloworld.txt,
另一次用于重定向 https://publicobject.com/helloworld.txt。

很明显的,网络拦截器的请求包含了更多信息,
比如 OkHttp 为了减少数据的传输时间以及传输流量而自动添加的请求头 Accept-Encoding:gzip 希望服务器能返回已压缩过的响应数据。

网络拦截器下的 Chain 具有一个非空的 Connection 对象,
它可以用来查询客户端所连接的服务器的IP地址以及TLS配置信息。

Chain的源码中也说明了只有在网络拦截器下的 chain 才能使用,
而应用拦截器下的 chain.connection() 总是返回 null。

应用拦截器和网络拦截器的区别

每种拦截器都有各自的优点:

应用拦截器

  • 不能操作中间的响应结果,比如重定向和重试,只能操作客户端主动的第一次请求以及最终的响应结果。
  • 始终调用一次,即使Http响应是从缓存中提供的。
  • 关注原始的request,而不关心注入的headers,比如If-None-Match。
  • 允许短路 short-circuit ,并且不调用 chain.proceed()。(注:这句话的意思是Chain.proceed()不需要一定要获取来自服务器的响应,但是必须还是需要返回Respond实例。那么实例从哪里来?答案是缓存。如果本地有缓存,可以从本地缓存中获取响应实例返回给客户端。这就是short-circuit (短路)的意思)
  • 允许请求失败重试,并多次调用 chain.proceed();

网络拦截器

  • 能够对重定向和重试等中间响应进行操作
  • 不允许调用缓存来short-circuit (短路)这个请求。(注:意思就是说不能从缓存池中获取缓存对象返回给客户端,必须通过请求服务的方式获取响应,也就是Chain.proceed())
  • 观察网络传输中数据传输和变化(注:比如当发生了重定向时,我们就能通过网络拦截器来确定存在重定向的情况)
  • 可以获取 Connection 携带的请求信息(即可以通过chain.connection() 获取非空对象)

常见拦截器


import java.io.IOException;

import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 添加缓存
 */
public class CacheInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        //第一步,获得chain内的request
        Request request = chain.request();

        //没网强制从缓存读取
        // (必须得写,不然断网状态下,退出应用,或者等待一分钟后,就获取不到缓存)
        if (!NetWorkUtils.isNetworkAvailable(MyApplication.getInstance())) {
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }

        //第二步,用chain执行request,获取response
        Response response = chain.proceed(request);
        Response responseLatest;
        if (NetWorkUtils.isNetworkAvailable(MyApplication.getInstance())) {
//            int maxAge = 0;// 有网络时 设置缓存超时时间0个小时
            int maxAge = 60; //有网失效一分钟
            responseLatest = response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control",
                            "public, max-age=" + maxAge)
                    .build();
        } else {
//            int maxStale = 60 * 60 * 24 * 28;// 无网络时,设置超时为4周
            int maxStale = 60 * 60 * 6; // 没网失效6小时
            responseLatest = response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control",
                            "public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }

        //第三步,返回response
        return responseLatest;
    }

}

import android.util.Log;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 添加请求头
 */
public class HeaderInterceptor implements Interceptor {

    private static final String TAG = HeaderInterceptor.class.getSimpleName();

    @Override
    public Response intercept(Chain chain) throws IOException {
        //第一步,获得chain内的request
        Request request = chain.request();

        Log.d(TAG, "intercept" + "处理开始");

        //第二步,用chain执行request,获取response
        Request.Builder builder = request.newBuilder();
        builder.addHeader("ROLE", "1")
                .addHeader("TOKEN", "")
                .addHeader("APPCLIENT", "android");
        Request requestBuild = builder.build();
        Response response = chain.proceed(requestBuild);

        Log.d(TAG, "intercept" + "处理完");

        //第三步,返回response
        return response;
    }
}

import android.util.Log;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 请求日志
 */
class LoggingInterceptor implements Interceptor {

    private static final String TAG = LoggingInterceptor.class.getSimpleName();

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        //第一步,获得chain内的request
        Request request = chain.request();

        //处理request信息
        long t1 = System.nanoTime();
        Log.d(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        //第二步,用chain执行request,获取response
        Response response = chain.proceed(request);

        //处理response信息
        long t2 = System.nanoTime();
        Log.d(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        //第三步,返回response
        return response;
    }
}
import java.io.IOException;
import java.util.Iterator;
import java.util.Set;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 加密query内容
 */
public class EncodeDataInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";

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

        HttpUrl url = request.url();
        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        sb.append(scheme).append(newHost).append(path).append("?");
        Set<String> queryList = url.queryParameterNames();
        Iterator<String> iterator = queryList.iterator();

        for (int i = 0; i < queryList.size(); i++) {
            String queryName = iterator.next();
            sb.append(queryName).append("=");
            String queryKey = url.queryParameter(queryName);
            //对query的key进行加密
            sb.append(CommonUtils.getMD5(queryKey));
            if (iterator.hasNext()) {
                sb.append("&");
            }
        }

        String newUrl = sb.toString();
        Request.Builder builder = request.newBuilder()
                .url(newUrl);

        return chain.proceed(builder.build());
    }
}


import java.io.IOException;
import java.util.Iterator;
import java.util.Set;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;

/**
 * 加密query内容
 */
public class EncodeBodyInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";

    public static String requestBodyToString(RequestBody requestBody) throws IOException {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        return buffer.readUtf8();
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        HttpUrl url = request.url();

        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        sb.append(scheme).append(newHost).append(path).append("?");
        Set<String> queryList = url.queryParameterNames();
        Iterator<String> iterator = queryList.iterator();

        for (int i = 0; i < queryList.size(); i++) {

            String queryName = iterator.next();
            sb.append(queryName).append("=");
            String queryKey = url.queryParameter(queryName);
            //对query的key进行加密
            sb.append(CommonUtils.getMD5(queryKey));
            if (iterator.hasNext()) {
                sb.append("&");
            }
        }

        String newUrl = sb.toString();

        RequestBody body = request.body();
        String bodyToString = requestBodyToString(body);
        TestBean testBean = GsonTools.changeGsonToBean(bodyToString, TestBean.class);
        String userPassword = testBean.getUserPassword();
        //加密body体中的用户密码
        testBean.setUserPassword(CommonUtils.getMD5(userPassword));

        String testGsonString = GsonTools.createGsonString(testBean);
        RequestBody requestBody = RequestBody.create(
                MediaType.parse("application/json"),
                testGsonString
        );


        Request.Builder builder = request.newBuilder()
                .post(requestBody)
                .url(newUrl);

        return chain.proceed(builder.build());
    }
}

import java.io.IOException;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * URL重定向
 */
public class HostInterceptor implements Interceptor {
    private String newHost = "127.0.0.1";

    @Override
    public Response intercept(Chain chain) throws IOException {
        //第一步,获得chain内的request
        Request request = chain.request();

        HttpUrl url = request.url();
        //http://127.0.0.1/test/upload/img?userName=xiaoming&userPassword=12345
        String scheme = url.scheme();//  http https
        String host = url.host();//   127.0.0.1
        String path = url.encodedPath();//  /test/upload/img
        String query = url.encodedQuery();//  userName=xiaoming&userPassword=12345

        StringBuffer sb = new StringBuffer();
        String newUrl = sb.append(scheme)
                .append(newHost)
                .append(path)
                .append("?")
                .append(query)
                .toString();
        Request.Builder builder = request.newBuilder()
                .url(newUrl);

        //第二步,用chain执行request,获取response
        //第三步,返回response
        return chain.proceed(builder.build());
    }
}

ByteStrings and Buffers

Okio是围绕这两种类型构建的,它们将大量功能打包到一个简单的API中:

ByteString是不可变的字节序列。
对于字符数据,最基本的就是String。
而ByteString就像是String的兄弟一般,它使得将二进制数据作为一个变量值变得容易。
这个类很聪明:它知道如何将自己编码和解码为十六进制、base64和utf-8。
Buffer是一个可变的字节序列。
像Arraylist一样,你不需要预先设置缓冲区的大小。
你可以将缓冲区读写为一个队列:将数据写到队尾,然后从队头读取。

在内部,ByteString和Buffer做了一些聪明的事情来节省CPU和内存。
如果您将UTF-8字符串编码为ByteString,它会缓存对该字符串的引用,这样,如果您稍后对其进行解码,就不需要做任何工作。

Buffer是作为片段的链表实现的。
当您将数据从一个缓冲区移动到另一个缓冲区时,它会重新分配片段的持有关系,而不是跨片段复制数据。
这对多线程特别有用:与网络交互的子线程可以与工作线程交换数据,而无需任何复制或多余的操作。

Sources and Sinks

java.io设计的一个优雅部分是如何对流进行分层来处理加密和压缩等转换。

Okio有自己的stream类型: Source和Sink,分别类似于java的Inputstream和Outputstream,

但是有一些关键区别:

超时(Timeouts)。
流提供了对底层I/O超时机制的访问。
与java.io的socket字流不同,read()和write()方法都给予超时机制。

易于实施。
source只声明了三个方法:read()、close()和timeout()。
没有像available()或单字节读取这样会导致正确性和性能意外的危险操作。

使用方便。
虽然source和sink的实现只有三种方法可写,
但是调用方可以实现Bufferedsource和Bufferedsink接口, 这两个接口提供了丰富API能够满足你所需的一切。

字节流和字符流之间没有人为的区别。
都是数据。你可以以字节、UTF-8字符串、big-endian的32位整数、little-endian的短整数等任何你想要的形式进行读写;
不再有InputStreamReader!

易于测试。
Buffer类同时实现了BufferedSource和BufferedSink接口,因此测试代码简单明了。

Sources 和 Sinks分别与InputStream 和 OutputStream交互操作。
你可以将任何Source看做InputStream ,也可以将任何InputStream 当做Source。对于Sink和Outputstream也是如此。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值