自研一个简易版本的OkHTTP

news2025/1/10 15:58:54

一,背景

为了彻底搞明白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;
        }

        @Override
        public 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() {
        @Override
        public 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() {
        @Override
        public 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和port
            if (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;

    @Override
    public 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.coloradmin.cn/o/1009476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【工具使用】STM32CubeMX-DMA配置(ADC+DMA 和 UART+DMA)

一、概述 无论是新手还是大佬&#xff0c;基于STM32单片机的开发&#xff0c;使用STM32CubeMX都是可以极大提升开发效率的&#xff0c;并且其界面化的开发&#xff0c;也大大降低了新手对STM32单片机的开发门槛。     本文主要讲述STM32芯片的DMA的配置及其相关知识。 二、…

离散制造企业如何打造MES管理系统

在当今制造业中&#xff0c;MES生产管理系统越来越受到关注&#xff0c;但在实际应用中也遇到了一些问题。本文分析了离散制造业和流程生产行业的MES应用现状&#xff0c;指出了这两个行业在部署MES管理系统时存在差异的原因&#xff0c;并探讨了如何在离散制造业提升生产效率&…

本地缓存 guava

缓存接口 集成guava本地缓存

前端Javascript模块化

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 引言 前端模块化的发展历程 1.全局函数式编程 2.命名空间模式 3.CommonJS require函数 module.exports 4.AM…

笔记(一)斯坦福CS224W图机器学习、图神经网络、知识图谱

节点和连接构成的图 如何对图数据进行挖掘&#xff1f; 传统机器学习&#xff0c;数据是独立同分布的&#xff0c;解决表格、矩阵、序列等问题 图机器学习处理连接的数据&#xff0c;需要满足以下几个方面&#xff1a; 1、图是任意尺寸输入2、图是动态变化的&#xff0c;有时…

Haproxy集群与常见的Web集群调度器

文章目录 1. Web集群调度器概述1.1 Web集群调度器简介1.2 调度器类别1.2.1 常用软件类1.2.2 常用硬件类 2. Haproxy软件介绍2.1 Haproxy简介2.2 支持功能2.3 主要特性2.4 常用调度算法2.4.1 轮询&#xff1a;RR&#xff08;Round Robin&#xff09;2.4.2 最小连接数&#xff1a…

软件测试与开发实训室建设方案

一 、系统概述 软件测试与开发实训室是软件开发过程中的一项重要测试活动&#xff0c;旨在验证不同软件模块或组件之间的集成与交互是否正常。综合测试确保各个模块按照设计要求正确地协同工作&#xff0c;以实现整个软件系统的功能和性能。以下是软件测试与开发实训室的一般流…

第一章 SQL Server 数据库部署

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。 个人主页&#xff1a;小李会科技的…

【黄色手套22】9话:栈和队列

目录 栈和队列的基本概念&#xff1a; 数据结构中的栈和队列&#xff1a; 栈和队列的基本结构&#xff1a; 1.栈和队列的结构示意图 2.栈和队列中数据的插入和删除 栈和队列的实现&#xff1a; 栈的实现 栈.c stack.h 源stack.c 队列的实现 队列.c queue.h queue.c…

使用branch and bound分支定界算法选择UTXO

BnB算法原理 分支定界算法始终围绕着一颗搜索树进行的&#xff0c;我们将原问题看作搜索树的根节点&#xff0c;从这里出发&#xff0c;分支的含义就是将大的问题分割成小的问题。 大问题可以看成是搜索树的父节点&#xff0c;那么从大问题分割出来的小问题就是父节点的子节点…

【办公自动化】用Python批量从上市公司年报中获取主要业务信息

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

电力系统优化:数字孪生的革新方法

数字孪生技术在电力系统中能够发挥令人瞩目的作用。这项技术不仅可以提高电力系统的可靠性和效率&#xff0c;还有助于降低能源浪费和环境影响。本文将深入探讨数字孪生技术在电力领域的多个关键应用领域&#xff0c;以展示其潜力和重要性。 数字孪生技术可以创建电力设备的虚拟…

农村怎样利用光伏发电?

随着经济快速发展&#xff0c;光伏、风电等可再生能源在农村地区普及率越来越高&#xff0c;极大推动了农业产业发展&#xff0c;提高了农民收入&#xff0c;改善了农村生态环境&#xff0c;促进了乡村各项工作健康发展。 光伏&#xff0b;农业是一种新兴的农业形式&#xff0…

如何获取美团的热门商品和服务

导语 美团是中国最大的生活服务平台之一&#xff0c;提供了各种各样的商品和服务&#xff0c;如美食、酒店、旅游、电影、娱乐等。如果你想了解美团的热门商品和服务&#xff0c;你可以使用爬虫技术来获取它们。本文将介绍如何使用Python和BeautifulSoup库来编写一个简单的爬虫…

文件上传漏洞~操作手册

目录 上传文件一般过滤方式 客服端校验 服务端校验 黑白名单机制 常规文件上传漏洞绕过 客户端绕过 1.游览器禁用JavaScript 2.正常burp suite抓包改包 服务端绕过 1.Content-Type绕过 2.黑名单绕过 1&#xff09;命名规则绕过 2&#xff09;大小写绕过 3&#x…

jvm的调优工具

1. jps 查看进程信息 2. jstack 查看进程的线程 59560为进程id 产生了死锁就可以jstack查看了 详细用途可以看用途 3. jmap 如何使用dump文件看下 查看 4.jstat 空间占用和次数 5. jconsole可视化工具 各种使用情况&#xff0c;以及死锁检测 6. visualvm可视化工具…

如何使用ArcGIS Pro提取河网水系

DEM数据除了可以看三维地图和生成等高线之外&#xff0c;还可以用于水文分析&#xff0c;这里给大家介绍一下如何使用ArcGIS Pro通过水文分析提取河网水系&#xff0c;希望能对你有所帮助。 数据来源 本教程所使用的数据是从水经微图中下载的DEM数据&#xff0c;除了DEM数据&a…

导入发运地点wsh_locations

当客户上的地址变更之后&#xff0c;发运事务处理上的地址还是原来的地址&#xff0c;发运上的地址来源table wsh_locations. 解决办法&#xff1a;运行接口请求&#xff0c;导入发运地点&#xff0c;日期可以范围包括地点变更的日期。 请求完成后再次查看地址发现地址已变更…

iwebsec靶场 文件包含漏洞通关笔记10-data伪协议利用

目录 前言 1.data伪协议 2.使用条件 第10关 data://伪协议利用 1.打开靶场 2.源码分析 3.渗透 &#xff08;1&#xff09;明文渗透 &#xff08;2&#xff09;base64编码渗透 前言 1.data伪协议 data协议和input协议差不多&#xff0c;指定data是get方法&#xff0c;…

从零基础到精通Flutter开发:一步步打造跨平台应用

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 导言 Flutter是一种流行…