自研一个简易版本的OkHTTP

一,背景

为了彻底搞明白okhttp原理,仿照okhttp自研一个

二,思路

业务上没发出一个request,使用AsyncCall包装起来,然后在网络分发器的作用下,执行具体的每一个Call,这些具体的Call会经过层层的拦截器,最终会调用到CallServiceInterceptor,这个对象里面有一个ConnectionPool,保存着每一个url对应的socket,最终处理的结果返回交给具体的每一个call,然后在回调到业务层

三,相关类图

四,具体代码实现

4.1 request

public class Request {private Map<String, String> headers;private String method;private HttpUrl url;private RequestBody body;public Request(Builder builder) {this.url = builder.url;this.method = builder.method;this.headers = builder.headers;this.body = builder.body;}public String method() {return method;}public HttpUrl url() {return url;}public RequestBody body() {return body;}public Map<String, String> headers() {return headers;}public final static class Builder {HttpUrl url;Map<String, String> headers = new HashMap<>();String method;RequestBody body;public Builder url(String url) {try {this.url = new HttpUrl(url);return this;} catch (MalformedURLException e) {throw new IllegalStateException("Failed Http Url", e);}}public Builder addHeader(String name, String value) {headers.put(name, value);return this;}public Builder removeHeader(String name) {headers.remove(name);return this;}public Builder get() {method = "GET";return this;}public Builder post(RequestBody body) {this.body = body;method = "POST";return this;}public Request build() {if (url == null) {throw new IllegalStateException("url == null");}if (TextUtils.isEmpty(method)) {method = "GET";}return new Request(this);}}
}

4.2 Response 

public class Response {int code;int contentLength = -1;Map<String, String> headers = new HashMap<>();String body;//保持连接boolean isKeepAlive;public Response() {}public Response(int code, int contentLength, Map<String, String> headers, String body,boolean isKeepAlive) {this.code = code;this.contentLength = contentLength;this.headers = headers;this.body = body;this.isKeepAlive = isKeepAlive;}public int getCode() {return code;}public int getContentLength() {return contentLength;}public Map<String, String> getHeaders() {return headers;}public String getBody() {return body;}public boolean isKeepAlive() {return isKeepAlive;}
}

4.3 HttpUrl

public class HttpUrl {String protocol;String host;String file;int port;public HttpUrl(String url) throws MalformedURLException {URL url1 = new URL(url);host = url1.getHost();file = url1.getFile();file = TextUtils.isEmpty(file) ? "/" : file;protocol = url1.getProtocol();port = url1.getPort();port = port == -1 ? url1.getDefaultPort() : port;}public String getProtocol() {return protocol;}public String getHost() {return host;}public String getFile() {return file;}public int getPort() {return port;}
}

4.4 Call

public class Call {Request request;HttpClient client;//是否执行过boolean executed;boolean cancel;public boolean isCancel() {return cancel;}public Request getRequest() {return request;}public Call(Request request, HttpClient client) {this.request = request;this.client = client;}public HttpClient getClient() {return client;}public void enqueue(Callback callback) {synchronized (this) {if (executed) {throw new IllegalStateException("已经执行过了,就不要执行");}executed = true;}//把任务交给调度器调度client.dispatcher().enqueue(new AsyncCall(callback));}/*** 是否取消*/public void cancel() {cancel = true;}/*** 执行网络请求的线程*/class AsyncCall implements Runnable {private Callback callback;public AsyncCall(Callback callback) {this.callback = callback;}@Overridepublic void run() {//是否回调过boolean singaledCallbacked = false;try {//执行真正的请求Response response = getResponse();if (cancel) {singaledCallbacked = true;callback.onFailure(Call.this, new IOException("客户端主动执行了cancel"));} else {singaledCallbacked = true;callback.onResponse(Call.this, response);}} catch (Exception e) {
//                e.printStackTrace();if (!singaledCallbacked) {//如果没有回调过callback.onFailure(Call.this, e);}} finally {//将这个任务从调度器移除client.dispatcher().finished(this);}}public String host() {return request.url().getHost();}}/*** 这里是重点!!!* @return*/private Response getResponse() throws Exception{//创建拦截器责任链List<Interceptor> interceptors = new ArrayList();//重试拦截器interceptors.add(new RetryInterceptor());//请求头拦截器interceptors.add(new HeaderInterceptor());//缓存拦截器interceptors.add(new CacheInterceptor());//连接拦截器interceptors.add(new ConnectionInterceptor());//通信拦截器interceptors.add(new CallServiceInterceptor());InterceptorChain chain = new InterceptorChain(interceptors, 0, this, null);return chain.process();}
}

4.5 InterceptorChain

public class InterceptorChain {List<Interceptor> interceptors;int index;Call call;HttpConnection httpConnection;public InterceptorChain(List<Interceptor> interceptors, int index, Call call, HttpConnection connection) {this.interceptors = interceptors;this.index = index;this.call = call;this.httpConnection = connection;}public Response process(HttpConnection httpConnection) throws IOException{this.httpConnection = httpConnection;return process();}public Response process() throws IOException {if (index >= interceptors.size()) throw new IOException("Interceptor China index out max length");//获得拦截器 去执行Interceptor interceptor = interceptors.get(index);InterceptorChain next = new InterceptorChain(interceptors, index + 1, call, httpConnection);Response response = interceptor.intercept(next);return response;}
}

4.6 Interceptor

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

4.7 ConnectionPool

public class ConnectionPool {private static final String TAG = "ConnectionPool";private static final boolean DEBUG = BuildConfig.DEBUG;private final long keepAlive;private boolean cleanrunning;private Deque<HttpConnection> connections = new ArrayDeque<>();public ConnectionPool() {this(1, TimeUnit.MINUTES);}public ConnectionPool(long keepAlive, TimeUnit unit) {this.keepAlive = unit.toMillis(keepAlive);}private Runnable cleanupRunable = new Runnable() {@Overridepublic void run() {while (true) {long waitDuration = cleanup(System.currentTimeMillis());if (waitDuration == -1) {return;}if (waitDuration > 0) {synchronized (ConnectionPool.this) {try {ConnectionPool.this.wait(waitDuration);} catch (InterruptedException e) {e.printStackTrace();}}}}}};private long cleanup(long currentTimeMillis) {long longgetIdleDuration = -1;synchronized (this) {Iterator<HttpConnection> iterator = connections.iterator();while (iterator.hasNext()) {HttpConnection connection = iterator.next();long idleDuration = currentTimeMillis - connection.getLastUseTime();//超过了最大允许闲置时间if (idleDuration > keepAlive) {if (DEBUG) Log.d(TAG, "ConnectionPool cleanup: " + "超出闲置时间,移除连接池");iterator.remove();connection.close();continue;}//没有超过闲置时间//记录 最长的闲置时间if (longgetIdleDuration < idleDuration) {longgetIdleDuration = idleDuration;}}//假如keepAlive是10s//if (longgetIdleDuration >= 0) {return keepAlive - longgetIdleDuration;}//连接池中没有连接cleanrunning = false;return longgetIdleDuration;}}private static final Executor executer = new ThreadPoolExecutor(0, Integer.MAX_VALUE,60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r, "Connection Pool");thread.setDaemon(true);//设置为守护线程,可以理解为跟进程同样的生命周期return thread;}});public void put(HttpConnection httpConnection) {//如果没有执行清理线程if (!cleanrunning) {cleanrunning = true;executer.execute(cleanupRunable);}connections.add(httpConnection);}public synchronized HttpConnection get(String host, int port) {Iterator<HttpConnection> iterator = connections.iterator();while (iterator.hasNext()) {HttpConnection connection = iterator.next();//如果查找到连接池始终在相同的host和portif (connection.isSameAddress(host, port)) {iterator.remove();return connection;}}return null;}}

4.8  CallServiceInterceptor

public class CallServiceInterceptor implements Interceptor {private static final String TAG = "CallServiceInterceptor";private static final boolean DEBUG = BuildConfig.DEBUG;@Overridepublic Response intercept(InterceptorChain chain) throws IOException {if (DEBUG) Log.d(TAG, "CallServiceInterceptor intercept: " + "通信拦截器");HttpConnection connection = chain.httpConnection;HttpCode httpCode = new HttpCode();InputStream inputStream = connection.call(httpCode);String statusLine = httpCode.readLine(inputStream);Map<String, String> headers = httpCode.readHeaders(inputStream);int contentLength = -1;if (headers.containsKey("Content-Length")) {//如果有content-length,代表可以拿到响应体的字节长度contentLength = Integer.valueOf(headers.get("Content-Length"));}boolean isChunked = false;if (headers.containsKey("Transfer-Encoding")) {//如果有有Transfer-Encoding,表示是分块编码,此时没有响应体的长度isChunked = headers.get("Transfer-Encoding").equalsIgnoreCase("chunked");}String body = null;if (contentLength > 0) {byte[] bytes = httpCode.readBytes(inputStream, contentLength);body = new String(bytes);} else if (isChunked) {body = httpCode.readChunked(inputStream);}String[] status = statusLine.split(" ");boolean isKeepAlive = false;if (headers.containsKey("Connection")) {isKeepAlive = headers.get("Connection").equalsIgnoreCase("Keep-Alive");}connection.updateLastUseTime();return new Response(Integer.valueOf(status[1]), contentLength, headers, body,isKeepAlive);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/111576.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】多线程案例-阻塞队列

1. 前言 阻塞队列&#xff08;BlockingQueue&#xff09;是一个支持两个附加操作的队列。这两个附加的操作是&#xff1a; 在队列为空时&#xff0c;获取元素的线程会等待队列变为非空当队列满时&#xff0c;存储元素的线程会等待队列可用 阻塞队列常用于生产者和消费者的场…

精益求精:Android应用体积优化的终极指南

精益求精&#xff1a;Android应用体积优化的终极指南 1. 介绍 在当今移动应用生态系统中&#xff0c;Android应用的体积优化是开发者需要高度重视的关键方面之一。一个庞大的应用体积不仅会对用户体验造成负面影响&#xff0c;还会导致以下问题&#xff1a; 下载速度延迟&am…

Re-Learn Linux Part1

1. Linux的目录结构 在Linux文件系统中有两个特殊的目录&#xff1a; 一个用户所在的工作目录&#xff0c;也叫当前目录&#xff0c;可以使用一个点 . 来表示&#xff1b;另一个是当前目录的上一级目录&#xff0c;也叫父目录&#xff0c;可以使用两个点 .. 来表示。 . &#…

Linux 忘记root密码解决方法(CentOS7.9)

忘记Linux系统的root密码&#xff0c;可以不用重新安装系统&#xff0c;进入单用户模式重新更改一下root密码即可。 步骤如下&#xff1a; 首先&#xff0c;启动Linux系统&#xff0c;进入开机界面&#xff0c;在界面中按"e"进入编辑界面&#xff0c;动作要快。按&q…

P2P协议的传输艺术

TP 采用两个 TCP 连接来传输一个文件。 控制连接&#xff1a;服务器以被动的方式&#xff0c;打开众所周知用于 FTP 的端口 21&#xff0c;客户端则主动发起连接。该连接将命令从客户端传给服务器&#xff0c;并传回服务器的应答。常用的命令有&#xff1a;list——获取文件目…

如何在 Excel 中进行加,减,乘,除

在本教程中&#xff0c;我们将执行基本的算术运算&#xff0c;即加法&#xff0c;减法&#xff0c;除法和乘法。 下表显示了我们将使用的数据以及预期的结果。 | **S / N** | **算术运算符** | **第一个号码** | **第二个号码** | **结果** | | 1 | 加法&#xff08;&#xff…

探索Redis速度之谜

Redis&#xff0c;作为一款高性能的内存数据库&#xff0c;一直以来都因其出色的速度而闻名。然而&#xff0c;Redis的速度之快究竟源自何处&#xff0c;其中隐藏着怎样的奥秘&#xff1f;在这篇博客中&#xff0c;我们将深入探索Redis速度之谜&#xff0c;揭开其快速性能背后的…

华为云云耀云服务器L实例评测|使用宝塔10分钟部署一个围猫猫小游戏

目录 前言一、选择华为云云耀云服务器L实例的原因二、华为云云耀云服务器的优势三、快速部署一个小游戏&#xff08;1&#xff09;终端部署1、使用Termius工具连接终端2、安装Nginx3、上传打包文件 &#xff08;2&#xff09;宝塔可视化面板部署1、进入宝塔2、宝塔菜单3、上传代…

千兆以太网网络层 ARP 协议的原理与 FPGA 实现

文章目录 前言一、ARP 帧的应用场景和存在目的二、ARP 帧工作原理三、以太网 ARP 帧发包实例设计四、以太网 CRC校验代码五、以太网 ARP 帧发包测试---GMII1.模拟数据发送2.仿真模块3.仿真波形六、以太网 ARP 帧发包测试---RGMII1.顶层文件2 .仿真代码七、上板测试(RGMII)前言…

Java的序列化

写在前面 本文看下序列化和反序列化相关的内容。 源码 。 1&#xff1a;为什么&#xff0c;什么是序列化和反序列化 Java对象是在jvm的堆中的&#xff0c;而堆其实就是一块内存&#xff0c;如果jvm重启数据将会丢失&#xff0c;当我们希望jvm重启也不要丢失某些对象&#xff…

css自学框架之图片懒加载

首先解释一下什么叫图片懒加载。图片懒加载是一种在页面加载时&#xff0c;延迟加载图片资源的技术&#xff0c;也就是说图片资源在需要的时候才会加载&#xff0c;就是在屏幕显示范围内加载图片&#xff0c;屏幕显示范围外图片不加载。 一、关键函数 用到的关键函数&#xf…

The driver has not received any packets from the server

在测试数据迁移时遇到的错误。 目录 一、错误 二、解决 三、数据迁移测试 3.1 环境 3.2 源码及测试 3.2.1 源码 3.2.2 测试结果&#xff08;太慢&#xff09; 3.2.3 源码修改 3.2.4 异常及解决 一、错误 The driver has not received any packets from the server. 二…