自研一个简易版本的OkHTTP

2023-11-04

一,背景

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自研一个简易版本的OkHTTP 的相关文章

  • 如何让“不适当的阻塞方法调用”变得适当?

    我目前正在尝试更多地利用 kotlin 协程 但我面临一个问题 当在这些协程中使用 moshi 或 okhttp 时 我收到警告 不适当的阻塞方法调用 解决这些问题的最佳方法是什么 我真的不想变得不合适 该警告是关于阻止当前线程和协程无法正
  • 流式传输 okhttp 响应正文

    我正在实施一个服务器发送的事件 http www w3schools com html html5 serversentevents asp使用 OkHttp 的库 服务器发送事件的工作原理是与服务器保持开放的 HTTP 连接 在服务器上
  • 当代码为 401 时,如何在 okhttp 中获取响应正文?

    我正在使用 OkHttp 3 2 0 这里是构建请求对象的代码 MediaType JSON MediaType parse AppConstants CONTENT TYPE VALUE JSON RequestBody body Req
  • 使用 Okhttp 在多部分 POST 中传递数组

    我正在构建一个需要 API 调用的应用程序 该调用在其 POST 正文请求中包含数组 我正在使用 OkHttp 2 6 来请求 API Postman 中的请求如下所示 我尝试过几种写作方式RequestBody为了实现这一目标 第一种方法
  • 如何信任具有交叉签名根的 SSL 证书,在 android <= 5 上已过期

    我在一家使用 Comodo Sectigo SSL 证书的公司工作 但是突然间 我们的应用程序在使用 Okhttp 客户端将 POST 发送到服务器时开始抛出此错误 在 Android 4 和 5 版本中 HTTP FAILED javax
  • 访问 OKHttp 响应正文

    所以我需要弄清楚如何在第二个响应中访问我从第一个响应中获得的值 我认为我可以将其存储到一个变量中并在另一个请求中访问它 然而 情况似乎并非如此 这是给我带来问题的一点 因此 我的第一个请求是获取一个令牌 然后我需要在第二个请求中使用存储在
  • 我可以使用 OkHttp 将本地 IP 地址绑定到我的 SSLSocketFactory 吗?

    我正在努力让 Android 上的 OkHttpClient 使用自定义证书发出 HTTPS 请求 同时绑定到特定网络接口的本地地址 我目前的尝试使用以下内容OkHttpClient val client OkHttpClient Buil
  • 如何更有效地通过 http 下载大文件?

    我正在尝试在 Kotlin 中下载大文件 这个问题 除了我使用 Kotlin 而不是 java 语法略有不同 val client OkHttpClient val request Request Builder url urlString
  • 如何使用 OkHttp 3.2.0 在 Picasso 2.5.2 中添加基本身份验证

    我正在使用 picasso 2 5 2 库从远程服务器下载位图 图像 url 需要标头中的基本身份验证 我已经尝试过以下 SO 答案 但它们都不适用于最新的 picasso 和 OkHttp 库 答案 1 https stackoverfl
  • OkHttp如何设置最大连接池大小(不是最大空闲连接)

    在 OkHttp 中 我找不到设置硬最大连接池大小的方法 从文档中https square github io okhttp 3 x okhttp okhttp3 ConnectionPool html https square githu
  • 在 OkHttp java 中创建承载授权标头

    我需要使用OkHttp3在java中作为HTTP客户端并在请求中发送授权标头 example 授权 持票人 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 eyJpc3MiOiJodHRaswczovL2F1dGg
  • Android 上的 OkHttp PublicKey 固定

    有谁知道我们如何使用 OkHttp3 实现公钥固定 一直在阅读有关 SSL 固定的内容 我发现我们可以使用证书固定或公钥固定 后者似乎更灵活 来实现 但我只能找到例子证书固定 https github com square okhttp w
  • 模拟 Retrofit 响应调用,但 Call 不起作用

    我正在嘲笑 APIService 的响应 不幸的是 它不起作用 我必须回电 但我不明白如何做 问题是如何发回 Call 对象 RunWith AndroidJUnit4 class class ApiServiceTest Test fun
  • 仅在某些 Android 设备上抛出 java.net.SocketTimeoutException

    我正在尝试从 Android 设备上传 JPEG 图像文件 我正在使用square okhttp用于创建请求的库 我在联想 Yoga 平板电脑上遇到这个问题 当我尝试上传图像时 出现以下异常 但当我在 Samsung Galaxy Tab
  • OkHttp 和 Retrofit,并发请求刷新 token

    在我的应用程序中 我实现了 Retrofit 来调用 WebServices 并使用 OkHttp 来使用拦截器和身份验证器 有些请求需要token https jwt io 并且我已经实现了 Authenticator 接口来处理刷新 遵
  • android OkHttpClient请求错误

    我正在尝试使用我的 Android 应用程序在 mysql 数据库中存储一些数据 我正在使用 okhttp3 发送请求 但在这一行出现错误 client newCall request execute 我在本地机器和在线上尝试过 但它给了我
  • 使用 Retrofit 2 添加标头以请求

    我正在尝试发送带有身份验证标头的请求 但服务器似乎无法识别客户端 我用了this https futurestud io tutorials android basic authentication with retrofit教程 并实现了
  • 如何向 OkHttp 请求拦截器添加标头?

    我将这个拦截器添加到我的 OkHttp 客户端 public class RequestTokenInterceptor implements Interceptor Override public Response intercept C
  • 改造将多个图像上传到单个密钥

    我正在使用 Retrofit 将图像上传到我的服务器 这里我需要为一个密钥上传多个图像 我已经尝试使用 Postman 网络客户端 它运行良好 这是一个屏幕截图 以下是请求的键值对 调查图像 文件1 文件2 文件3 属性图像 文件DRA j
  • OkHttp如何获取Json字符串?

    Solution 这是我这边的一个错误 正确的方法是响应 body string 以外响应 body toString 我使用 Jetty servlet URL 是http 172 16 10 126 8789 test path jso

随机推荐

  • python实现斐波拉契数列函数

    简单介绍一下 斐波拉契数列 斐波那契数列 Fibonacci sequence 又称黄金分割数列 因数学家莱昂纳多 斐波那契 Leonardoda Fibonacci 以兔子繁殖为例子而引入 故又称为 兔子数列 指的是这样一个数列 0 1
  • 企业架构LNMP学习笔记10

    1 Nginx版本 在实际的业务场景中 需要使用软件新版本的功能 特性 就需要对原有软件进行升级或重装系统 Nginx的版本需要升级迭代 那么如何进行升级呢 线上服务器如何升级 我们选择稳定版本 从nginx的1 14版本升级到nginx的
  • Git的简介和使用

    本文来自数据学习网 https www datalearner com 专注于机器学习方法 数据挖掘技术和编程技术 原文地址 https www datalearner com blog 1051521123408432 Git是一个版本控
  • IOException异常的处理方式

    首先看一段代码 这段代码有明显的IO异常 一般我们的做法是捕获异常 public static void main String args try 文件可能不存在 FileWriter fw new FileWriter W demo tx
  • 第5章 团队开发管理-测验题-作业

    1 在软件开发的各种资源中 D 是最重要的资源 A开发工具 B方法 C硬件环境 D人员 2 在攻克技术难题时 最佳的开发团队组织模型是 A A民主式结构 B主程序员式结构 C矩阵式结构 D以上所有选项都不是 3 在选择开发团队组织结构时应考
  • Java高级系列——异常(Exception)

    在解释Java中的异常时 首先我们来看一张图 上图是我们Java中 异常类的一个继承关系图 从图中我们可以看到Java标准库内构建的这些通用的异常 他们都是以Throwable为顶层父类 Throwable又派生出Error类和Except
  • 【idea】指定要排除拼写检查的单词

    问题 在使用idea的时候会自动检查我们的单词拼写是否错误 这是个方便又不方便的功能 因为我们有时候会使用一些特殊的命名 比如 AlipayConfig 那现在我们来修改一下设置 方法 1 鼠标悬停在该单词上 按 Alt enter组合键
  • mysql c3p0 参数_mysql Communications link failure,C3p0的参数详解

    MySQL默认一个连接空闲8小时候就会自动断开 而这时程序以为连接还能使用 然后在使用的时候就会出现Communications link failure异常 这时需要进行两步设置 有时候只设置MySQL就可以了 一 在MySQL的配置文件
  • Java开源的规则引擎 Drools 电商行业实战(含完整代码)

    前言 我所在项目组刚好接到一个领取优惠券需求 具体需求是用户领取的各种类型的优惠券 比如 代金券 折扣券 数量不能超过某个自定义数量 因考虑到领取限制数量是动态的 另外考虑到扩展性 满足将来业务规则的增长 不只是限制领取数需要新加其他条件
  • 2023年信号处理与机器学习国际研讨会(WSPML 2023)

    会议简介 Brief Introduction 2023年信号处理与机器学习国际研讨会 WSPML 2023 会议时间 2023年9月22 24日 召开地点 中国 杭州 大会官网 www wspml org 2023年信号处理与机器学习国际
  • 【目标检测适用】批量修改xml文件中的name字段

    前言 使用labelimg进行标注的时候 由于都是用的是默认的名称 有时候类的名字会出现拼写错误 比如我想要写的是 cow 结果打上去的是 cwo 一出错就错一片 这很常见 所以参考了 https www jianshu com p cf1
  • MySQL基础之DCL语句

    DCL Data Control Language 语句 数据控制语句 用途 控制数据库 表 字段 用户的访问权限和安全级别 常用关键字 grant revoke等 一般用于管理数据库和用户的权限 通过实用例子来学习grant 分配权限 和
  • vue禁止长按屏幕复制内容

    App vue 禁止长按屏幕复制内容 webkit touch callout none webkit user select none moz user select none ms user select none user selec
  • 【HTML】HTML面试知识梳理

    目录 DOCTYPE 文章类型 head标签 浏览器乱码的原因及解决 常用的meta标签与SEO script标签中defer和async的区别 src href区别 HTML5有哪些更新 语义化标签 媒体标签 表单 进度条 度量器 DOM
  • 刷脸支付降低实现数字商业的难度

    移动支付和银行卡支付没有办法确定使用者到底是谁 因为可以和家人等共同使用 刷脸支付可以确定消费实体是谁 定位到具体人确定数据标签 一位新零售从业者认为 刷脸支付除了提供更便捷的支付服务外 还可以提供更多的商业数据用于精细化经营 从垂直领域来
  • Java并发基础知识

    基础概念 什么是进程和线程 进程是程序运行资源分配的最小单位 是具有一定独立功能的程序关于某个数据集合上的一次运行活动 进程是系统进行资源分配和调度的一个独立单位 线程是 CPU 调度的最小单位 必须依赖于进程而存在 与同属一个进程的其 他
  • CUDA之矩阵乘法——globalmemory

    CUDA 矩阵乘法 使用global memory 报错 错误 17 error no instance of overloaded function cudaMalloc matches the argument list E Niki
  • Python requests实现图片上传接口自动化测试

    最近帮别人写个小需求 需要本地自动化截图 然后图片自动化上传到又拍云 实现自动截图非常简单 在这里就不详细介绍了 主要和大家写下 如何通过Python requests实现上传本地图片到又拍云服务器 话不多说 因为我们要使用requests
  • Linux——FTP服务器搭建及访问

    FTP是文件传输协议的英文简称 其用于Internet上的控制文件的双向传输 同时 他也是一个应用程序 基于不同的操作系统有不同的FTP应用程序 而所有这些应用程序都遵守同一种协议以传输文件 互联网上提供文件存储进而访问服务的计算机 他们依
  • 自研一个简易版本的OkHTTP

    一 背景 为了彻底搞明白okhttp原理 仿照okhttp自研一个 二 思路 业务上没发出一个request 使用AsyncCall包装起来 然后在网络分发器的作用下 执行具体的每一个Call 这些具体的Call会经过层层的拦截器 最终会调