一、简介

Square 公司开源的 OkHttp

okhttp 官网 https://square.github.io/okhttp/

1、特性

1)支持HTTP2,对一台机器的所有请求共享同一个socket连接(同域名下所有通信都在单个连接上完成,消除了因多个连接而带来的延时和内存消耗)。

2)内置连接池,支持连接复用,减少请求延迟

3)透明的GZIP压缩减少响应数据的大小

4)通过缓存避免重复请求

5)失败时自动重连,自动重定向

当网络出现问题的时候OkHttp依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP,OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接,当握手失败时会回退到TLS 1.0。

使用:OkHttp 是一个适用于 Android、Kotlin 和 Java 应用的 HTTP 和 HTTP/2 客户端,它的请求/响应 API 使用构造器模式 builders 来设计,它支持阻塞式的同步请求和带回调的异步请求。

如果你使用 OkHttpClient,你不用重写你的代码,okhttp-urlconnection 模块实现了 java.net.HttpURLConnection 中的API,okhttp-apache 模块实现了 HttpClient 中的API

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.3</version>
</dependency>

持续更新,和上面的同版本更新

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp-urlconnection</artifactId>
    <version>4.9.3</version>
</dependency>

2016 停止更新

<dependency>
    <groupId>com.squareup.okhttp</groupId>
    <artifactId>okhttp-apache</artifactId>
    <version>2.7.5</version>
</dependency>
二、导入依赖
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>
三、创建 get 请求

因为是测试,OkHttpClient 是新建的对象,所以不需要配置类,如果需要 bean 工厂可以添加配置类步骤七,工具类步骤八

    @Test
    public void getUrlParams() throws IOException {
        OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)//设置连接超时时间
                .readTimeout(60, TimeUnit.SECONDS)//设置读取超时时间
                .build();
        String url = "http://127.0.0.1:8080/rest/get/url_path_params?name=admin&id=98";
        Request.Builder builder = new Request.Builder();
        Request request = builder.url(url).build();
        Response response = okHttpClient.newCall(request).execute();
        ResponseBody body = response.body();
        System.out.println(body.string());
    }
四、创建 post 请求

post 请求传递 json 数据 Content-Type:application/json; charset=utf-8

    @PostMapping("/post/params_json_list")
    public JSONObject postParamsJsonList(@RequestBody List<Map<String, Object>> list) {
        JSONObject jsonObject = JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"type\":\"okhttp3\"}");
        jsonObject.put("body", list);
        return jsonObject;
    }

RequestBody 封装 body 参数

    @Test
    public void postParamsJson() throws IOException {
        OkHttpClient build = new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS).build();
        String json = "{\"name\": \"admin\",\"id\": 10}";
        String url = "http://127.0.0.1:8080/okhttp3/post/params_json";
        RequestBody requestBody = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
        Request request = new Request.Builder().post(requestBody).url(url).build();
        Response execute = build.newCall(request).execute();
        ResponseBody responseBody = execute.body();
        assert responseBody != null;
        log.info(responseBody.string());
    }
五、创建异步请求(MultipartBody 表单)

form 表单上传表单(含文件)Content-Type:multipart/form-data;

@PostMapping("/post/params_from_async")
    public JSONObject postParamsFromAsync(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws InterruptedException, IOException, ServletException {
        TimeUnit.SECONDS.sleep(30);
        JSONObject jsonObject;
        TreeMap<String, Object> treeMap = new TreeMap<>();
        Collection<Part> parts = request.getParts();
        parts.forEach((part) -> {
            if (part.getContentType() == null) {
                treeMap.put(part.getName(), request.getParameter(part.getName()));
            }
        });
        String s = UUID.randomUUID().toString().replace("-", "").substring(0, 5) + Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().indexOf("."));
        try {
            file.transferTo(new File("I:\\spring\\spring-boot-aop-test\\file\\" + s));
            jsonObject = JSON.parseObject("{\"message\":\"文件下载成功\",\"code\":200,\"type\":\"okhttp3\"}");
            jsonObject.put("body", treeMap);
        } catch (IOException e) {
            e.printStackTrace();
            jsonObject = JSON.parseObject("{\"message\":\"文件下载失败\",\"code\":201,\"type\":\"okhttp3\"}");
        }
        return jsonObject;
    }

测试方法

    @Test
    public void postParamsFromAsync() throws IOException {
        String url = "http://127.0.0.1:8080/okhttp3/post/params_from_async";

        File file = new File("C:\\Users\\Administrator\\Pictures\\图标\\influxdb-studio.png");
        RequestBody multipartBody = MultipartBody.create(file, MediaType.parse("image/png"));

        MultipartBody body = new MultipartBody.Builder()
                .addFormDataPart("file", "influxdb.png", multipartBody)
                .addFormDataPart("age", "18")
                .build();
        Request request = new Request.Builder()
                .post(body)
                .url(url)
                .addHeader("Connection", "keep-alive")
                .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                log.error("okhttp", "onFailure: " + e.getMessage());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                log.info("okhttp", "onResponse: " + response.body().string());
            }
        });
    }
六、FormBody 表单参数

form 表单上传表单 Content-Type:application/x-www-form-urlencoded

    @PostMapping("/post/params_from")
    public JSONObject postParamsFrom(HttpServletRequest request) throws InterruptedException, IOException, ServletException {
        JSONObject jsonObject;
        Map<String, Object> treeMap = new TreeMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        parameterMap.forEach((key, value) -> {
            if (value.length == 1) {
                treeMap.put(key, value[0]);
            } else {
                treeMap.put(key, value);
            }
        });
        jsonObject = JSON.parseObject("{\"message\":\"表单读取成功\",\"code\":200,\"type\":\"okhttp3\"}");
        if (treeMap.keySet().size() > 0) {
            jsonObject.put("body", treeMap);
        }
        return jsonObject;
    }

测试方法

    @Autowired
    OkHttpClient okHttpClient;
    @Test
    public void postParamsFrom() throws IOException {
        String url = "http://127.0.0.1:8080/okhttp3/post/params_from";
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("password", "123456");
        linkedHashMap.put("address", "西安");
        FormBody.Builder builder = new FormBody.Builder();
        builder.add("name", "admin");
        linkedHashMap.forEach((key, value) -> {
            builder.add(key, value);
        });
        RequestBody formBody = builder.build();

        Request request = new Request.Builder()
                .post(formBody)
                .url(url)
                .addHeader("Connection", "keep-alive")
                .build();
        Response response = okHttpClient.newCall(request).execute();
        log.info(response.body().string());
    }
七、OkhttpConfig 配置类

详细配置参考 https://www.freesion.com/article/3815946206/

@Configuration
@Slf4j
public class OkHttpConfig {

    @Bean
    public OkHttpClient okHttpClient() throws NoSuchAlgorithmException, KeyStoreException {
        // 详细配置可参考 https://www.freesion.com/article/3815946206/
        return new OkHttpClient.Builder()
                .connectTimeout(60, TimeUnit.SECONDS)      //设置连接超时
                .readTimeout(60, TimeUnit.SECONDS)         //设置读超时
                .writeTimeout(60, TimeUnit.SECONDS)        //设置写超时
                .retryOnConnectionFailure(true)                     //是否自动重连
                // 设置https配置,此处忽略了所有证书
                .sslSocketFactory(sslSocketFactory(), new EasyX509TrustManager(null))
                // 验证服务器的证书域名。在https握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                })
                // 拦截器日志记录
                .addInterceptor((Interceptor.Chain chain) -> {
                    Request request = chain.request();
                    log.info("intercept 接口地址:" + request.url()
                            + "\r\n接口参数):" + request.body().toString());
                    return chain.proceed(chain.request());
                })
                // 添加证书,一般不要设置
                .certificatePinner(new CertificatePinner.Builder()
                        .add("test1.com", "sha1/sdfsdsdsdsdsdsdsdsdsdsdds=")
                        .add("test2.com", "sha1/sdsdsdsdsdsdsdsdsdssdssds=")
                        .build())
                //.connectionSpecs(...) // 设置连接的规格、TLS版本和密码套件等,最好不要去设置
                //.socketFactory(new SocketFactory() {...}) // 使用定制的用于http请求的套接字
/*                .authenticator(new Authenticator() { // 添加授权证书
                    @Override
                    public Request authenticate(Route route, Response response) throws IOException {
                    }
                }) */
                .cache(new Cache(new File("cache.tmp"), 10 * 1024 * 1024)) // 10M缓存
                // 定义连接池,最多有五个空闲连接,每个空闲连接最多保持6分钟
                .connectionPool(new ConnectionPool(5, 6, TimeUnit.MINUTES))
                // 指定分发器,即异步执行http请求时的线程池,http响应的回调也是在此线程池的线程中执行
                // .dispatcher(new Dispatcher(new ThreadPoolExecutor(...)))
                .followRedirects(true)       // 允许http重定向
                .followSslRedirects(false)   // 截断https的重定向
                .pingInterval(30, TimeUnit.SECONDS) // 设置ping检测网络连通性的间隔。默认为0
                //.proxy(...) // 设置单个代理
                //.proxyAuthenticator(...)  // 设置代理验证
                //.proxySelector(...) // 为不同的链接设置不同的代理
                .build();

    }

    public class EasyX509TrustManager implements X509TrustManager {

        private X509TrustManager standardTrustManager = null;

        /**
         * Constructor for EasyX509TrustManager.
         */
        public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException,
                KeyStoreException {
            super();
            TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory
                    .getDefaultAlgorithm());
            factory.init(keystore);
            TrustManager[] trustmanagers = factory.getTrustManagers();
            if (trustmanagers.length == 0) {
                throw new NoSuchAlgorithmException("no trust manager found");
            }
            this.standardTrustManager = (X509TrustManager) trustmanagers[0];
        }

        /**
         * @see X509TrustManager#checkClientTrusted(X509Certificate[],
         * String authType)
         */
        public void checkClientTrusted(X509Certificate[] certificates, String authType)
                throws CertificateException {
            standardTrustManager.checkClientTrusted(certificates, authType);
        }

        /**
         * @see X509TrustManager#checkServerTrusted(X509Certificate[],
         * String authType)
         */
        public void checkServerTrusted(X509Certificate[] certificates, String authType)
                throws CertificateException {
            if ((certificates != null) && (certificates.length == 1)) {
                certificates[0].checkValidity();
            } else {
                standardTrustManager.checkServerTrusted(certificates, authType);
            }
        }

        /**
         * @see X509TrustManager#getAcceptedIssuers()
         */
        public X509Certificate[] getAcceptedIssuers() {
            return this.standardTrustManager.getAcceptedIssuers();
        }
    }

    public SSLSocketFactory sslSocketFactory() {
        try {
            //信任任何链接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new EasyX509TrustManager(null)}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        return null;
    }
}
八、OkhttpUtil 工具类
@Slf4j
@Component
public class OkHttpUtil{

    private static OkHttpClient  okHttpClient;

    @Autowired
    public OkHttpUtil(OkHttpClient  okHttpClient) {
        OkHttpUtil.okHttpClient= okHttpClient;
    }

    /**
     * get
     * @param url     请求的url
     * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
     */
    public static  String get(String url, Map<String, String> queries) {
        String responseBody = "";
        StringBuffer sb = new StringBuffer(url);
        if (queries != null && queries.keySet().size() > 0) {
            boolean firstFlag = true;
            Iterator iterator = queries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry<String, String>) iterator.next();
                if (firstFlag) {
                    sb.append("?" + entry.getKey() + "=" + entry.getValue());
                    firstFlag = false;
                } else {
                    sb.append("&" + entry.getKey() + "=" + entry.getValue());
                }
            }
        }
        Request request = new Request.Builder()
                .url(sb.toString())
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            log.error("okhttp3 put error >> ", e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * post
     *
     * @param url    请求的url
     * @param params post form 提交的参数
     * @return
     */
    public static String post(String url, Map<String, String> params) {
        String responseBody = "";
        FormBody.Builder builder = new FormBody.Builder();
        //添加参数
        if (params != null && params.keySet().size() > 0) {
            for (String key : params.keySet()) {
                builder.add(key, params.get(key));
            }
        }
        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            log.error("okhttp3 post error >> ", e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * get
     * @param url     请求的url
     * @param queries 请求的参数,在浏览器?后面的数据,没有可以传null
     * @return
     */
    public static String getForHeader(String url, Map<String, String> queries) {
        String responseBody = "";
        StringBuffer sb = new StringBuffer(url);
        if (queries != null && queries.keySet().size() > 0) {
            boolean firstFlag = true;
            Iterator iterator = queries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry<String, String>) iterator.next();
                if (firstFlag) {
                    sb.append("?" + entry.getKey() + "=" + entry.getValue());
                    firstFlag = false;
                } else {
                    sb.append("&" + entry.getKey() + "=" + entry.getValue());
                }
            }
        }
        Request request = new Request.Builder()
                .addHeader("key", "value")
                .url(sb.toString())
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            log.error("okhttp3 put error >> ", e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * Post请求发送JSON数据....{"name":"zhangsan","pwd":"123456"}
     * 参数一:请求Url
     * 参数二:请求的JSON
     * 参数三:请求回调
     */
    public static String postJsonParams(String url, String jsonParams) {
        String responseBody = "";
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            log.error("okhttp3 post error >> ", e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }

    /**
     * Post请求发送xml数据....
     * 参数一:请求Url
     * 参数二:请求的xmlString
     * 参数三:请求回调
     */
    public static String postXmlParams(String url, String xml) {
        String responseBody = "";
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml);
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
            int status = response.code();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            log.error("okhttp3 post error >> "+ e);
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return responseBody;
    }
}

九、小结

okhttp 创建 request 请求常见的 body 类型

  • RequestBody 适合传一个参数,如:json (2 和 3 示例)
  • FormBody 表单参数(仅支持文本) (示例 6)
  • MultipartBody 表单(支持文本 和 文件)(示例 4)

创建 okhttp 请求,主要生成 request(包含 method、url、body、header),最后通过 OkHttpClient 执行同步(execute)异步(enqueue)方法

        Request request = new Request.Builder()
                .post(formBody)
                .url(url)
                .addHeader("Connection", "keep-alive")
                .build();
        Response response = okHttpClient.newCall(request).execute();

1、OkHttp3使用起来简单,但是实际上其原理代码十分复杂,这也使得OkHttp3功能很强大,包括缓存、gzip、长连接、重定向等。

2、OkHttp3原理复杂,但是它的主要功能就是网络访问,网络访问就是要完成读写功能,于是我们只需要好好把握HttpEngine中的sendRequest作用和readResponse作用即可,即sendRequest完成初始化,并通过Socket方式建立同服务器的网络连接,而readResponse通过Socket完成读写功能。

3、OkHttp3依赖于Okio,主要体现在网络访问数据放在okio.Buffer中,通过okio.Okio write方法写服务器数据,通过okio.Okio read方法读取服务器返回数据。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐