系列文章
比如缓存,cookie,拦截器等等如何自定义---
零散的点
- 使用EventListener对HTTP请求关键步骤进行监控
- 深入理解OkHttp3:(七)事件(Events)
- 基于OkHttp的Http监控
- 本地简单示例
public void testEventListener(View view) { OkHttpClient client = new OkHttpClient.Builder() //在使用OkHttpClient.Builder时候设置自定义EventListener .eventListener(new MyEventListener()) .build(); Request request = new Request.Builder().url("https://www.baidu.com/").build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG,"testEventListener onFailure"); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG,"testEventListener onResponse \n " + response.body().string()); } }); } class MyEventListener extends EventListener{ @Override public void callStart(Call call) { Log.d(TAG,"testEventListener MyEventListener callStart"); super.callStart(call); } @Override public void dnsStart(Call call, String domainName) { Log.d(TAG,"testEventListener MyEventListener dnsStart"); super.dnsStart(call, domainName); } @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) { Log.d(TAG,"testEventListener MyEventListener dnsEnd"); super.dnsEnd(call, domainName, inetAddressList); } @Override public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) { Log.d(TAG,"testEventListener MyEventListener connectStart"); super.connectStart(call, inetSocketAddress, proxy); } @Override public void secureConnectStart(Call call) { Log.d(TAG,"testEventListener MyEventListener secureConnectStart"); super.secureConnectStart(call); } @Override public void secureConnectEnd(Call call, @Nullable Handshake handshake) { Log.d(TAG,"testEventListener MyEventListener secureConnectEnd"); super.secureConnectEnd(call, handshake); } @Override public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) { Log.d(TAG,"testEventListener MyEventListener connectEnd"); super.connectEnd(call, inetSocketAddress, proxy, protocol); } @Override public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol, IOException ioe) { Log.d(TAG,"testEventListener MyEventListener connectFailed"); super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe); } @Override public void connectionAcquired(Call call, Connection connection) { Log.d(TAG,"testEventListener MyEventListener connectionAcquired"); super.connectionAcquired(call, connection); } @Override public void connectionReleased(Call call, Connection connection) { Log.d(TAG,"testEventListener MyEventListener connectionReleased"); super.connectionReleased(call, connection); } @Override public void requestHeadersStart(Call call) { Log.d(TAG,"testEventListener MyEventListener requestHeadersStart"); super.requestHeadersStart(call); } @Override public void requestHeadersEnd(Call call, Request request) { Log.d(TAG,"testEventListener MyEventListener requestHeadersEnd"); super.requestHeadersEnd(call, request); } @Override public void requestBodyStart(Call call) { Log.d(TAG,"testEventListener MyEventListener requestBodyStart"); super.requestBodyStart(call); } @Override public void requestBodyEnd(Call call, long byteCount) { Log.d(TAG,"testEventListener MyEventListener requestBodyEnd"); super.requestBodyEnd(call, byteCount); } @Override public void responseHeadersStart(Call call) { Log.d(TAG,"testEventListener MyEventListener responseHeadersStart"); super.responseHeadersStart(call); } @Override public void responseHeadersEnd(Call call, Response response) { Log.d(TAG,"testEventListener MyEventListener responseHeadersEnd"); super.responseHeadersEnd(call, response); } @Override public void responseBodyStart(Call call) { Log.d(TAG,"testEventListener MyEventListener responseBodyStart"); super.responseBodyStart(call); } @Override public void responseBodyEnd(Call call, long byteCount) { Log.d(TAG,"testEventListener MyEventListener responseBodyEnd"); super.responseBodyEnd(call, byteCount); } @Override public void callEnd(Call call) { Log.d(TAG,"testEventListener MyEventListener callEnd"); super.callEnd(call); } @Override public void callFailed(Call call, IOException ioe) { Log.d(TAG,"testEventListener MyEventListener callFailed"); super.callFailed(call, ioe); } } log: 2020-10-29 23:45:54.461 D/OkHttpActivity: testEventListener MyEventListener callStart 2020-10-29 23:45:54.479 D/OkHttpActivity: testEventListener MyEventListener dnsStart 2020-10-29 23:45:54.502 D/OkHttpActivity: testEventListener MyEventListener dnsEnd 2020-10-29 23:45:54.505 D/OkHttpActivity: testEventListener MyEventListener connectStart 2020-10-29 23:45:54.528 D/OkHttpActivity: testEventListener MyEventListener secureConnectStart 2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener secureConnectEnd 2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener connectEnd 2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener connectionAcquired 2020-10-29 23:45:54.630 D/OkHttpActivity: testEventListener MyEventListener requestHeadersStart 2020-10-29 23:45:54.630 D/OkHttpActivity: testEventListener MyEventListener requestHeadersEnd 2020-10-29 23:45:54.631 D/OkHttpActivity: testEventListener MyEventListener responseHeadersStart 2020-10-29 23:45:54.656 D/OkHttpActivity: testEventListener MyEventListener responseHeadersEnd 2020-10-29 23:45:54.656 D/OkHttpActivity: testEventListener MyEventListener responseBodyStart 2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener responseBodyEnd 2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener connectionReleased 2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener callEnd 2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener onResponse <!DOCTYPE html> <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc>***</body> </html>
- OkHttp可以设置:
- 可以同时执行的最大最大数量. 默认是64.
- 单个域名/host可以同时执行的最大请求数量. 默认是5.
public final class Dispatcher { private int maxRequests = 64; private int maxRequestsPerHost = 5; *** } OkHttpClient client = new OkHttpClient.Builder().build(); client.dispatcher().setMaxRequests(200); client.dispatcher().setMaxRequestsPerHost(10); - maxRequests 及 maxRequestsPerHost 生效位置,以enqueue为例
OkHttpClient client = new OkHttpClient.Builder().build(); Request request = new Request.Builder().url("https://www.baidu.com/").build(); client.newCall(request).enqueue(*** -> okhttp3/RealCall.java @Override public void enqueue(Callback responseCallback) { *** //创建AsyncCall实例,执行Dispatcher.enqueue client.dispatcher().enqueue(new AsyncCall(responseCallback)); } -> okhttp3/Dispatcher.java void enqueue(AsyncCall call) { synchronized (this) { //将AsyncCall实例添加到Deque/readyAsyncCalls中 readyAsyncCalls.add(call); } promoteAndExecute(); } -> private boolean promoteAndExecute() { *** synchronized (this) { //遍历readyAsyncCalls for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); //这里 maxRequests 和 maxRequestsPerHost 起作用了 //如果执行中的异步请求数量已经>=最大请求数量,则中断遍历 if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. //如果Call的域名正在执行的请求数量已经>=单个域名最大请求数量,忽略该请求 if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity. //从readyAsyncCalls中将当前AsyncCall实例移除,防止后续请求来了,已经执行的请求重复执行 i.remove(); //请求数量未达到最大限制,则将AsyncCall实例加入executableCalls及runningAsyncCalls executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); //这里执行'异步网络请求' asyncCall.executeOn(executorService()); } return isRunning; }
- OkHttpClient中的配置
- connectionSpecs:指定Socket连接的配置,即使用HTTP还是HTTPS.
- 如果是HTTPS链接,则ConnectionSpec指定了使用的TLS版本 及 密钥算法套件.
- CLEARTEXT就是使用HTTP,不使用HTTPS. 明文和HTTP完全是一个意思.
okhttp3/ConnectionSpec.java /** Unencrypted, unauthenticated connections for {@code http:} URLs. */ public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
- final CertificateChainCleaner certificateChainCleaner
- CertificateChainCleaner 用于对TLS连接中的一系列证书信息进行整理
- 忽略和当前TLS握手过程无关的证书.
- 将有用的证书进行排序,组成证书链,便于客户端进行证书验证.
- 证书链中的第一个证书就是目标网站的证书
- 第二个是证书签发机构的证书,***
- 最后1个是本地根证书
- 参考HTTPS连接建立过程中的证书验证步骤
- CertificateChainCleaner 用于对TLS连接中的一系列证书信息进行整理
- final CertificatePinner certificatePinner
- CertificatePinner用于做证书验证/自签名证书验证.
- public Builder add(String pattern, String... pins) {
- Each pin is a hash of a certificate's Subject Public Key
- add方法配置的是证书的公钥
- 本地示例:先随便填1,看报错信息,然后修改后试验
String hostName = "www.baidu.com"; CertificatePinner pinner = new CertificatePinner.Builder() //这里胡乱填 .add(hostName,"sha256/fgsdklhgksdhghjsklg") .build(); OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(pinner) .build(); Request request = new Request.Builder().url("https://" + hostName).build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG,"testCertificatePinner onFailure \n " + e.getMessage()); e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG,"testCertificatePinner onResponse \n " + response.body().string()); } }); --> log: 2020-10-30 01:20:10.875 D/OkHttpActivity: testCertificatePinner onFailure Certificate pinning failure! Peer certificate chain: sha256//558pd1Y5Vercv1ZoSqOrJWDsh9sTMEolM6T8csLucQ=: CN=baidu.com,O=Beijing Baidu Netcom Science Technology Co.\, Ltd,OU=service operation department,L=beijing,ST=beijing,C=CN sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic5/V3IRQLNFFc7v4=: CN=GlobalSign Organization Validation CA - SHA256 - G2,O=GlobalSign nv-sa,C=BE sha256/K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE Pinned certificates for www.baidu.com: sha256/fgsdklhgksdhghjsklg= 根据log显示的信息修改,请求成功: CertificatePinner pinner = new CertificatePinner.Builder() .add(hostName,"sha256//558pd1Y5Vercv1ZoSqOrJWDsh9sTMEolM6T8csLucQ=") .add(hostName,"sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic5/V3IRQLNFFc7v4=") .add(hostName,"sha256/K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=") .build(); --> log: 2020-10-30 01:27:41.439 D/OkHttpActivity: testCertificatePinner onResponse <!DOCTYPE html>***
- final Authenticator authenticator
- Authenticator用于自动重新认证. 401代表当前请求需要用户验证 或 用户认证失败. 配置authenticator之后,在收到401后,会直接调用authenticator,在header中加入Authorization自动重新发起请求.
- Authorization两种主流方式:Basic , Bearer
- 使用authenticator实现Basic认证:
- 使用Credentials,直接填入 username,password . 实际上Credentials就是将username:password做了base64和Basic 做了拼接.
public void showAuthenticatorBasic(){ OkHttpClient client = new OkHttpClient.Builder() .authenticator(new Authenticator() { @Nullable @Override public Request authenticate(@Nullable Route route, Response response) throws IOException { //使用Credentials工具类自动生成Basic认证字符串 //例如 HuanHaiLiuXin:Password String credential = Credentials.basic("HuanHaiLiuXin", "Password"); return response.request().newBuilder() .header("Authorization", credential) .build(); } }) .build(); Request request = new Request.Builder().url("https://www.baidu.com/").build(); client.newCall(request).enqueue(***); } okhttp3/Credentials.java public static String basic(String username, String password, Charset charset) { String usernameAndPassword = username + ":" + password; String encoded = ByteString.encodeString(usernameAndPassword, charset).base64(); return "Basic " + encoded; }
- 使用Credentials,直接填入 username,password . 实际上Credentials就是将username:password做了base64和Basic 做了拼接.
- 使用authenticator实现Basic认证:
- 要自己实现字符串的拼接: "Bearer " + bearer token
public void showAuthenticatorBearer(){ OkHttpClient client = new OkHttpClient.Builder() .authenticator(new Authenticator() { @Nullable @Override public Request authenticate(@Nullable Route route, Response response) throws IOException { //假设bearerToken是我们通过OAuth2/refreshtoken获取到的bearer token String bearerToken = "fhdskh"; String bearerAuthorization = "Bearer " + bearerToken; return response.request().newBuilder() .header("Authorization", bearerAuthorization) .build(); } }) .build(); Request request = new Request.Builder().url("https://www.baidu.com/").build(); client.newCall(request).enqueue(); }
- 要自己实现字符串的拼接: "Bearer " + bearer token
- 避免再次重试,比如之前已经自动添加过Authorization,第二次重试还是验证不通过,可以判断后返回null
@Nullable @Override public Request authenticate(@Nullable Route route, Response response) throws IOException { String credential = Credentials.basic("HuanHaiLiuXin", "Password"); if(credential.equals(response.request().header("Authorization"))){ //说明之前已经正确配置过Authorization,仍然认证失败,返回null,放弃重试. return null; } return response.request().newBuilder() .header("Authorization", credential) .build(); } - 设置指定次数认证失败后,不再重试. 利用Response.priorResponse判断是第几次认证失败.
@Nullable @Override public Request authenticate(@Nullable Route route, Response response) throws IOException { String credential = Credentials.basic("HuanHaiLiuXin", "Password"); if(gainPriorResponseCount(response) >= 3){ //如果已经是第三次认证失败,则不再重试 return null; } return response.request().newBuilder() .header("Authorization", credential) .build(); } public int gainPriorResponseCount(Response response) { int count = 1; while ((response = response.priorResponse()) != null) { count++; } return count; }
- connectionSpecs:指定Socket连接的配置,即使用HTTP还是HTTPS.
- OkHttp中3个重要概念: Connection , Stream , Call
- OkHttpClient中所有配置项作用简要描述,思维导图
- 所有拦截器