OKHTTP的拦截器以及https访问

一、前言

        上一篇博文我们介绍了okhttp的基础用法,我们还可以进一步的配置,使用起来更加方便,我主要从配置拦截器和缓存,访问https几个方面讲解,本文还是会以okhttp的wiki部分例子作为本文的示例代码。

二、OKHTTP配置

  • Interceptors拦截器
            拦截器是一种强大的机制,可以监视、重写和重试调用。我们可以用拦截器做很多事情,添加我们自己的头部信息,设置有网请求,没网走缓存等。拦截器分为两种拦截器,一种是应用拦截器,一种是网络拦截器,两种拦截器有什么区别呢,我们怎么选择呢,下面我给大家简单地演示一哈。

    Application Interceptors 应用拦截器
    应用拦截器一般使用最多的是打印日志,下面简单的看下示例代码:

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

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

            Response response = chain.proceed(request);

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

            return response;
        }
    }

        下面直接调用

 public void run() 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();
        response.body().close();
    }

        打印出来的结果是
OKHTTP的拦截器以及https访问_第1张图片

        该拦截器只是在请求发出前和请求发出后分别打印出log日志,我们可以看到打印出了响应头信息,还有请求的时间差,connection打印出来的是null,这个用网络拦截器才能打印出来返回值。chain是一个接口,接口定义如下:

interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }

        三个参数,比较重要的是前两个参数,request和response,Response response=chain.proceed(request); 这个很重要,它是将我们拦截器串联起来的关键,我们在实现拦截器的时候,需要返回一个response,就是调用该方法,添加请求头啊,什么的才能被执行,上面请求的URL地址是 http://www.publicobject.com/helloworld.txt 通过重定向被重定向到了这个地址http://www.publicobject.com/helloworld.txt。okhttp将会自动跟随这个重定向,应用拦截器被调用一次,响应通过chain.proceed(request); 返回重定向的响应。

        我们可以在每个请求里面指定一个详细的User-Agent ,我们在分析日志的时候更有用了。默认情况下,okhttp的User-Agent 包含了okhttp的版本信息,我们可以通过拦截器来取代这个默认值,比如StackOverflow的一段参考代码:

 public final class UserAgentIntercept implements Interceptor{
        private static final String USER_AGENT_HEADER_NAME = "User-Agent";
        private final String userAgentHeaderValue;

        public UserAgentIntercept(String userAgentHeaderValue) {
            this.userAgentHeaderValue = userAgentHeaderValue;
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            final Request originalRequest = chain.request();
            return chain.proceed(originalRequest.newBuilder()
                    .removeHeader(USER_AGENT_HEADER_NAME)
                    .addHeader(USER_AGENT_HEADER_NAME, userAgentHeaderValue)
                    .build());
        }
    }

        这个userAgentHeaderValue 可以实我们版本号,版本信息,android的OS值等。
2.Network Interceptors 网络拦截器
网络拦截器我们可以打印出来更多的信息,我们用上面的拦截器,只不过调用发生了改变:

OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new LoggingInterceptor())
                .build();

        打印出来的值如下所示:
OKHTTP的拦截器以及https访问_第2张图片

OKHTTP的拦截器以及https访问_第3张图片

        我们看到打印出来的信息多了很多,connection在response里也打印出来了,当我们运行这段代码时,拦截器运行两次。第一次是初始化请求到这个地址 http://www.publicobject.com/helloworld.txt的时候调用,另一个用于重定向到 https://publicobject.com/helloworld.txt的时候。网络拦截器跟应用拦截器最大的区别是网络拦截器能够操作中间的响应,如重定向和重试,而应用拦截器不需要担心中间过程的响应。

        网络拦截器我们可以这样用,我们在请求的时候附上我们的token:

public final class TokenInterceptor implements Interceptor{
        private static final String USER_TOKEN = "Authorization";
        private final String token;

        public TokenInterceptor(String token) {
            this.token = token;
        }
        @Override
        public Response intercept(Chain chain) throws IOException {
            final Request originalRequest = chain.request();
            if(token == null || originalRequest.header("Authorization") != null){
                return chain.proceed(originalRequest);
            }
            Request request = originalRequest.newBuilder()
                    .header(USER_TOKEN,token)
                    .build();
            return chain.proceed(request);
        }
    }

        上面我们判断token为不为空,有时候还没有请求到token或者登陆的时候不需要token,这个时候直接请求就好了。如果我们请求的header已经有token了,那么也不需要在添加token了,token的键一般是Authorization ,如果不是可以修改成你的值。如果我们在请求的时候遇到了需要刷新token,返回了401 Not Authorised ,你可以看上一篇okhttp使用完全解析中处理身份验证的相关用法。

        我们还可以实现有网请求,无网走缓存,代码如下所示:

 public final class CacheControlInterceptor implements Interceptor{
        @Override
        public Response intercept(Chain chain) throws IOException {
            Response originalResponse = chain.proceed(chain.request());
            if (isNetWorkAvailable(context)) {
                int maxAge = 120; // 在线缓存在2分钟内可读取
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .build();
            } else {
                int maxStale = 60 * 60 * 24 * 14; // 离线时缓存保存2周
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
        }
    };

        网络拦截器和应用拦截器的区别主要有以下几点:

        应用拦截器:
        1. 不需要担心中间过程的响应,如重定向和重试.
        2. 总是只调用一次,即使HTTP响应是从缓存中获取.
        3. 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
        4. 允许短路而不调用 Chain.proceed(),即中止调用.
        5. 允许重试,使 Chain.proceed()调用多次.

        网络拦截器:
        1. 能够操作中间过程的响应,如重定向和重试.
        2. 当网络短路而返回缓存响应时不被调用.
        3. 只观察在网络上传输的数据.
        4. 携带请求来访问连接.

        每个拦截器都有自己的优点,如何选择就看你的业务逻辑了,不过我们一般通过应用拦截器打印日志,网络拦截器添加token啊,走缓存等。

  • OKHTTP访问https
            okhttp访问https也是很简单的,如果是访问CA机构颁发证书的网站,比如https://github.com/ ,默认情况下是可以信任的。但是有些自签名的证书访问就不行了,一般都是有两种办法,一种是默认信任所有的证书,客户端不做任何检查,这种简单快捷但是不安全;还有一种就是让android客户端信任自签名的证书,比如12306网站的证书。

  • 默认信任所有的证书:

public SSLContext getSLLContext() {
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }}, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return  sslContext;
    }
 OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(getSLLContext().getSocketFactory())
                .build();
  • android客户端信任自签名证书:
public SSLContext getSLLContext(){
        SSLContext sslContext = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream cerInputStream = getApplicationContext().getAssets().open("12306.cer");
            Certificate ca = cf.generateCertificate(cerInputStream);

            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
            TrustManager[] trustManagers = tmf.getTrustManagers();

            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagers, null);
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  sslContext;
    }

        我们把12306的证书放到assets下面,这样就可以读取成功了,okhttp的配置还是一样的。

参考

拦截器实现原理
android如何使用https
HTTPS理论基础及其在Android中的最佳实践

你可能感兴趣的:(Android开发)