OkHttp发送HTTP、(验证/忽略CA的)HTTPS请求示例

编程入门 行业动态 更新时间:2024-10-14 10:44:08

OkHttp发送HTTP、(验证/忽略CA的)HTTPS请求<a href=https://www.elefans.com/category/jswz/34/1770116.html style=示例"/>

OkHttp发送HTTP、(验证/忽略CA的)HTTPS请求示例

声明:相对来说,前面两个准备工作才是关键,OkHttp的使用示例反而不是那么重要了。

提示:建议直接去Github上将本人的测试项目下载下来(链接见文末),自己运行一下测试方法,进行观察,这样学
          习效率极佳。


准备工作之提供HTTP/HTTPS都可访问的API:

提示:此准备工作教会大家如何利用jdk自带的keytool生成keystore及证书,进而提供出供HTTPS访问的接口。

第一步:在jdk安装目录下的bin目录下,打开shell窗口

第二步:生成keystore

keytool -genkey -alias dengshuai_ca -keypass dengshuai@123*Ds -keyalg RSA -keysize 2048 -validity 360 -storetype PKCS12 -keystore C:/Users/JustryDeng/Desktop/dsstore.p12 -storepass dengshuai@123*Ds -dname "CN=dengshuai,OU=aspire,O=aspire,L=beijing,S=beijing,C=cn" -ext SAN=IP:127.0.0.1,DNS:localhost,IP:10.8.106.45,DNS:www.justrydeng

生成文件,如图所示:

注:keystore是仓库文件,文件类型可以多样,反正都指的是keystore。如这里生成的是.p12为后缀名的keystore,
       常见的还有以.jks等为后缀名的keystore。

注:上述指令中,两处密码可以不一致,本人为了方便,干脆就设置成一样的密码了。

注:对上述指令中的【-ext SAN=IP:127.0.0.1,DNS:localhost,IP:10.8.106.45,DNS:www.justrydeng】进行
       简单说明:设置这些拓展参数的目的是,配置hostname,当客户端访问服务端时,如果这里配置了hostname
                         验证的话,那么HTTPS请求的url中,ip/域名,就必须是这里设置的hostname之一,会报hostname
                         验证不通过(HTTP请求没影响)。还有一种方式就是,客户端可以主动设置不需要hostname验证,
                        那么这里生成keystore时,就可以不需要【-ext ......】指令。
       追注:这几个地址是服务器的地址,之所以有这么多,是考虑到:当客户端与服务端是在同一台机器上时,客户
                  端访问服务端走的要么是127.0.0.1,要么走的是localhost;当客户端与服务端是在不同一台机器上时,但
                  是处于同一个内网时,客户端访问服务端走的是服务端的内网地址(这里的10.8.106.45即为本人服务端的
                 内网地址);当客户端与服务端是处在公网上时,客户端访问服务端走的是服务端的公网地址(这里的
                  www.justrydeng即为本人服务端的内公网地址)。

注:keytool的相关参数指令,这里不再细聊,可参考本章第三节或自行查阅相关资料。

注:keystore主要用来存放密钥与证书,它也可以用来生成证书。

注:这个证书是给服务端用的。在本文中,会在这个准备工作中用到。

第三步:根据keystore生成证书

keytool -export -keystore C:/Users/JustryDeng/Desktop/dsstore.p12 -alias dengshuai_ca -file C:/Users/JustryDeng/Desktop/ds.crt -rfc

生成文件如图所示:

注:这个证书是给客户端用的。在本文中,会在OkHttp使用Https验证CA进行访问时用到。

第四步:将第二步生成的keystore拷贝至SpringBoot项目中,并作一下配置

将keystore拷贝到项目中:

配置application.properties文件:

# 此端口为HTTPS端口
server.port=9527
# 秘钥证书库文件所在位置
server.ssl.key-store=classpath:server/dsstore.p12
# 密码
server.ssl.key-store-password=dengshuai@123*Ds
# 秘钥证书库类型
server.ssl.keyStoreType=PKCS12
# 别名条目
server.ssl.keyAlias=dengshuai_ca# http端口设置为8080,具体设置方式见启动类
http.port=8080

注:到此为止HTTPS就配置好了,接下来再配置HTTP。

在启动类中,再配置HTTP:

import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;/*** 启动类** @author JustryDeng* @date 2019/6/5 13:04*/
@SpringBootApplication
public class OkhttpDemoApplication {public static void main(String[] args) {SpringApplication.run(OkhttpDemoApplication.class, args);}@Value("${http.port}")private Integer httpPort;@Beanpublic ServletWebServerFactory servletContainer() {TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();tomcat.addAdditionalTomcatConnectors(createHTTPConnector());return tomcat;}private Connector createHTTPConnector() {Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");connector.setScheme("http");connector.setSecure(false);connector.setPort(httpPort);return connector;}}

第五步:验证一下,启动启动类,可看见控制台输出

提示:接下来正常编写controller即可;然后使用https时访问9527端口;使用http时访问8080端口。


准备工作之封装OkHttpClient工具类:

提示:此准备工作教会大家如何(根据证书)获得SSLSocketFactory、TrustManager,这两个类的实例是进行HTTPS
           访问的关键所在。不论用的是什么工具(OkHttp、HttpClient、RestTemplate等等)进行的HTTPS访问,只要
           掌握如何获得SSLSocketFactory实例和TrustManager实例,那么无论是使用什么工具进行的HTTPS访问,
           问题都不大。设置过程大同小异罢了。

说明:因为客户端是进行HTTP访问,还是进行不验证CA的HTTPS访问,还是进行验证CA的HTTPS访问,是在获取
           HTTP客户端实例时,就决定了的,所以这里干脆将其提取成为一个工具类,方便管理。

说明:此工具类中因为有lombok和OkHttpClient,所以需要引入相关依赖(核心的SSLSocketFactory、TrustManager
           是不需要额外引入依赖的,因为本人时一OkHttp进行示例封装的,所以需要引入OkHttp的依赖),这些依赖将
           在【OkHttp发送HTTP、HTTPS请求示例】时给出。

注:很多东西直接在工具类中用注释的方式进行了说明,这里就直接上代码了。

import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;import javax.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;/*** OkHttpClient工具类** 注: OkHttp官方文档并不建议我们创建多个OkHttpClient,建议使用同一个实例; 所以此工具类采取单例模式** 注: 此工具类主要功能是 封装了 获取HTTP、HTTPS(不需验证CA)、HTTPS(需验证CA)请求客户端的实例,*     对此客户端的属性设置的比较少,只设置了 【连接超时时间】、【读超时时间】、【写超时时间】、【连接池】,*      可根据自己的实际项目情况, 灵活设置这些属性的属性值,还可以添加其它的属性设置** @author JustryDeng* @date 2019/6/11 20:50*/
@Slf4j
public class OkHttpClientUtil {/*** HTTP实例* <p>* 确保本条指令不会因编译器的优化而省略,且要求每次直接读值*/private static volatile OkHttpClient httpClient;/*** HTTPS实例(不需要校验CA)* <p>* 确保本条指令不会因编译器的优化而省略,且要求每次直接读值*/private static volatile OkHttpClient httpsClient;/** ssl socket工厂(不需要校验CA) */private static SSLSocketFactory sslSocketFactory = null;private static X509TrustManager trustManager = null;/*** HTTPS实例(需要校验CA)* <p>* 确保本条指令不会因编译器的优化而省略,且要求每次直接读值*/private static volatile OkHttpClient verifyCaHttpsClient;/** ssl socket工厂(需要校验CA) */private static SSLSocketFactory sslSocketFactoryVerifyCa = null;private static X509TrustManager trustManagerVerifyCa = null;/*** 超时时间等参数设置*/private static final int CONNECT_TIMEOUT = 60;private static final int READ_TIMEOUT = 100;private static final int WRITE_TIMEOUT = 60;/*** 获取http客户端** @return OkHttpClient客户端实例* @date 2019/6/11 20:36*/public static OkHttpClient getHttpClient() {if (httpClient == null) {synchronized (OkHttpClientUtil.class) {if (httpClient == null) {initHttpClient();}}}return httpClient;}/*** 获取https客户端(不需要校验证书)** @return OkHttpClient客户端实例* @date 2019/6/11 20:36*/public static OkHttpClient getHttpsClient() throws Exception {if (httpsClient == null) {synchronized (OkHttpClientUtil.class) {if (httpsClient == null) {initHttpsClient(false, null, null);}}}return httpsClient;}/*** 获取https客户端(需要校验证书)** @param caInputStream*         CA证书(此证书应由要访问的服务端提供)* @param cAalias*         别名*         注意:别名应该是唯一的, 别名不要和其他的别名一样,否者会覆盖之前的相同别名的证书信息。别名即key-value中的key。* @return OkHttpClient客户端实例* @date 2019/6/11 20:36*/public static OkHttpClient getHttpsClient(InputStream caInputStream, String cAalias) throws Exception {if (verifyCaHttpsClient == null) {synchronized (OkHttpClientUtil.class) {if (verifyCaHttpsClient == null) {// 如果需要校验证书,那么【证书】和【别名】不能为空if (caInputStream == null || cAalias == null) {throw new RuntimeException("[ca] and [alias] must not be null!");}initHttpsClient(true, caInputStream, cAalias);}}}return verifyCaHttpsClient;}/*** 初始化HTTP客户端** @date 2019/6/11 16:12*/private static void initHttpClient() {// 进行数据初始化OkHttpClient.Builder builder = new OkHttpClient.Builder()// 设置读取超时时间.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)// 设置写的超时时间.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)// 设置连接超时时间.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)// 使用连接池.connectionPool(pool());httpClient = builder.build();}/*** 初始化HTTPS客户端** @param needVerifyCa*         是否需要检验CA证书(即:是否需要检验服务器的身份)* @param caInputStream*         CA证书。(若不需要检验证书,那么此处传null即可)* @param cAalias*         别名。(若不需要检验证书,那么此处传null即可)*         注意:别名应该是唯一的, 别名不要和其他的别名一样,否者会覆盖之前的相同别名的证书信息。别名即key-value中的key。* @date 2019/6/11 16:12*/private static void initHttpsClient(boolean needVerifyCa, InputStream caInputStream, String cAalias)throws CertificateException, NoSuchAlgorithmException, KeyStoreException,KeyManagementException, IOException {// 先调用helper方法,初始化httpsHelper(needVerifyCa, caInputStream, cAalias);// 进行数据初始化OkHttpClient.Builder builder = new OkHttpClient.Builder()// 设置读取超时时间.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)// 设置写的超时时间.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)// 设置连接超时时间.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)// 使用连接池.connectionPool(pool());// 需要校验证书if (needVerifyCa) {builder.sslSocketFactory(sslSocketFactoryVerifyCa, trustManagerVerifyCa);/** 情况一: 证书中预设有 hostname*    证书中会预设一些hostname(即: 预设一些ip、域名), 只有当请求url中的ip/域名,是包含在预设的那些hostname中的时,* 才会认证通过,否者会报【The certificate hostname does not match】之类的问题。如果证书中预设有一些hostname* 的话,我们这里就不需要在再设置hostname校验规则了,会走默认的hostname校验。*** 情况二: 证书中没有预设 hostname*    如果连证书中都没有预设hostname的话,即:服务端认为不需要验证hostname, 这时,我们作为客户端,需要重写hostname校验规则,* 不让其走默认的校验规则(因为默认会对hostname进行校验)。即:不论是什么,直接返回true即可,表示所有的hostname都成功,* 在此处代码里,只需要builder.hostnameVerifier((String hostname, SSLSession session) -> true)即可达到设* 置不作hostname校验的效果。*    当然,如果你非要校验,你也可以自己在客户端通过builder.hostnameVerifier(HostnameVerifier hostnameVerifier)* 设置hostname校验规则。*/verifyCaHttpsClient = builder.build();return;}// 不需要校验证书builder.sslSocketFactory(sslSocketFactory, trustManager);// 不校验 url中的hostname(直接返回true,表示不校验hostname)// 注:hostname 指 ip/域名builder.hostnameVerifier((String hostname, SSLSession session) -> true);httpsClient = builder.build();}/*** 使用连接池,复用HTTP/HTTPS连接* <p>* Sets the connection pool used to recycle HTTP and HTTPS connections.** @date 2019/6/11 19:35*/private static ConnectionPool pool() {return new ConnectionPool(100, 5, TimeUnit.MINUTES);}/*** HTTPS辅助方法, 为HTTPS请求 创建SSLSocketFactory实例、TrustManager实例** @param needVerifyCa*         是否需要检验CA证书(即:是否需要检验服务器的身份)* @param caInputStream*         CA证书。(若不需要检验证书,那么此处传null即可)* @param cAalias*         别名。(若不需要检验证书,那么此处传null即可)*         注意:别名应该是唯一的, 别名不要和其他的别名一样,否者会覆盖之前的相同别名的证书信息。别名即key-value中的key。* @throws NoSuchAlgorithmException*         异常信息* @throws CertificateException*         异常信息* @throws KeyStoreException*         异常信息* @throws IOException*         异常信息* @throws KeyManagementException*         异常信息* @date 2019/6/11 19:52*/private static void httpsHelper(boolean needVerifyCa, InputStream caInputStream, String cAalias)throws CertificateException, NoSuchAlgorithmException, KeyStoreException,IOException, KeyManagementException {// https请求,需要校验证书if (needVerifyCa) {KeyStore keyStore = getKeyStore(caInputStream, cAalias);TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());trustManagerFactory.init(keyStore);TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));}trustManagerVerifyCa = (X509TrustManager) trustManagers[0];// 这里传TLS或SSL其实都可以的SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, new TrustManager[]{trustManagerVerifyCa}, new SecureRandom());sslSocketFactoryVerifyCa = sslContext.getSocketFactory();return;}// https请求,不作证书校验trustManager = new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] arg0, String arg1) {}@Overridepublic void checkServerTrusted(X509Certificate[] arg0, String arg1) {// 不验证}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}};SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());sslSocketFactory = sslContext.getSocketFactory();}/*** 获取(密钥及证书)仓库* 注:该仓库用于存放 密钥以及证书** @param caInputStream*         CA证书(此证书应由要访问的服务端提供)* @param cAalias*         别名*         注意:别名应该是唯一的, 别名不要和其他的别名一样,否者会覆盖之前的相同别名的证书信息。别名即key-value中的key。* @return 密钥、证书 仓库* @throws KeyStoreException 异常信息* @throws CertificateException 异常信息* @throws IOException 异常信息* @throws NoSuchAlgorithmException 异常信息* @date 2019/6/11 18:48*/private static KeyStore getKeyStore(InputStream caInputStream, String cAalias)throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {// 证书工厂CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");// 秘钥仓库KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());keyStore.load(null);keyStore.setCertificateEntry(cAalias, certificateFactory.generateCertificate(caInputStream));return keyStore;}
}

OkHttp发送HTTP、HTTPS请求示例:

软硬件环境介绍Windows10、IntelliJ IDEA、SpringBoot 2.1.5.RELEASE。

准备工作:在pom.xml中引入相关依赖

给出本人完整的pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.aspire.okhttp</groupId><artifactId>okhttp-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>okhttp-demo</name><description>测试使用okhttp</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- .projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version><scope>provided</scope></dependency><!-- .squareup.okhttp3/okhttp --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>3.14.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

项目结构说明:

本人写了一个项目,既为服务端又为客户端。其中controller提供服务端接口,测试类作为客户端对controller进行访问

注:上述三个测试类,分别用来测试http请求、测试不验证ca的http请求测试、验证ca的https请求。这三个测试类主要
        的不同有两个,第一个不同是:测试http的请求方式是http,端口是8080;测试https的请求方式是https,端口是
        9527。第二个不同是:获取OkHttpClient时,调用的工具类的方法不同。

http测试是:

https(不验证ca)测试是:

https(验证ca)测试是:

注:从上图中可以知道,测试(验证ca的)https时,使用到了我们最开始准备的CA证书。本人将CA文件也放进了项目里,
       位置在

下面给出上述几个类的内容:

提示:启动类(OkhttpDemoApplication)、系统配置文件(application.properties)在前面均已给出,这里就不再重
           复给出了。

User:

JdController:

import com.aspire.okhttp.okhttpdemo.model.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;/*** controller** @author JustryDeng* @date 2019/6/10 9:50*/
@Slf4j
@RestController
@SuppressWarnings("all")
public class JdController {private static final String[] GENDER_ARR;private static final String[] MOTTO_ARR;/** 使用jaskson作为json转换器 */private ObjectMapper jacksonMapper = new ObjectMapper();/** 使用安全的随机数类 */private SecureRandom random = new SecureRandom();static {GENDER_ARR = new String[]{"男", "女"};MOTTO_ARR = new String[]{"我是一只小小小小鸟~嗷嗷!", "蚂蚁牙黑!", "我爱她轰轰烈烈不会忘~"};}/*** 用于get方法测试** @date 2019/6/10 9:52*/@GetMapping("/get/test")public User getOneController(@RequestParam("name") String name) throws JsonProcessingException {int age = random.nextInt(100);String gender = GENDER_ARR[random.nextInt(1)];String motto = MOTTO_ARR[random.nextInt(2)];User user = User.builder().name(name).age(age).gender(gender).motto(motto).build();log.info("getOneController - return -> {}", jacksonMapper.writeValueAsString(user));return user;}/*** 用于post方法测试one** @date 2019/6/10 9:52*/@PostMapping("/post/test/one")public String postOneController(@RequestBody User user) {log.info("postOneController - return -> {}", user);return user.toString();}/*** 用于post方法测试two** @date 2019/6/10 9:52*/@PostMapping("/post/test/two")public String postTwoController(@RequestParam("platform") String platform, String name, String subject) {String result = platform + " ^_^ " +  name + " ^_^ " + subject;log.info("postTwoController - return -> {}", result);return result;}/*** 用于post方法测试three** @date 2019/6/10 9:52*/@PostMapping("/post/test/three")public String postThreeController(InputStream is) throws IOException {ByteArrayOutputStream baos = new ByteArrayOutputStream();int i;while ((i = is.read()) != -1) {baos.write(i);}String result = "流转换为字符串后\t" + baos.toString();log.info("postThreeController - return -> {}", result);return result;}/*** 用于post方法测试four** 注:单个文件的话,形参处直接使用MultipartFile即可** @date 2019/6/10 9:52*/@PostMapping("/post/test/four")public String postFourController(@RequestParam("fileName") MultipartFile[] files) throws IOException {StringBuilder sb = new StringBuilder(128);for (MultipartFile file : files) {sb.append("\nfile.getName()").append(" = ").append(file.getName());sb.append("\nfile.getContentType()").append(" = ").append(file.getContentType());sb.append("\nfile.getOriginalFilename()").append(" = ").append(file.getOriginalFilename());// 文件大小单位默认为字节sb.append("\nfile.getSize()").append(" = ").append(file.getSize()).append("B");sb.append("\n");}log.info("postFourController - return -> {}", sb);return sb.toString();}
}

HttpDemo:

import com.aspire.okhttp.okhttpdemo.model.User;
import com.aspire.okhttp.okhttpdemo.util.OkHttpClientUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import okio.BufferedSink;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;/*** http示例** @author JustryDeng* @date 2019/6/6 23:37*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpDemo {private static final Logger logger = LoggerFactory.getLogger(HttpDemo.class);/** 使用jaskson作为json转换器 */private ObjectMapper jacksonMapper = new ObjectMapper();private static OkHttpClient client;static {client = OkHttpClientUtil.getHttpClient();}/*** get-同步*/@Testpublic void getTestOne() throws IOException {// 地址必须要写上http/httpsString httpUrl = "http://127.0.0.1:8080/get/test?name=JustryDeng";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();// 响应体ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();/** 响应体的 string() 方法对于小文档来说十分方便、高效。* 但是如果响应体太大(超过1MB),应避免使用 string()方法 ,* 因为他会将把整个文档加载到内存中。对于超过1MB的响应body,* 应使用流的方式来处理body。*/// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** get-异步** 注:在一个工作线程中下载文件,当响应可读时回调Callback接口。读取*    响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。*/@Testpublic void getTestTwo() throws InterruptedException {// 地址必须要写上http/httpsString httpUrl = "http://127.0.0.1:8080/get/test?name=邓沙利文";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();//异步client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {logger.error("请求失败!请求相关信息为 -> {}!", call.request(), e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {String responseStr = response.body() == null ? null : response.body().string();logger.info("请求成功!响应Response为 -> {}!, 响应体数据为 -> {}!", response, responseStr);}});// 为了观察异步线程的日志输出, 这里不妨让当前线程休眠几秒TimeUnit.SECONDS.sleep(5);}/*** post-同步-提交请求体数据为json等类型的数据*/@Testpublic void postTestOne() throws IOException {MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");// 地址必须要写上http/httpsString url = "http://127.0.0.1:8080/post/test/one";User user = User.builder().name("张三").age(12).gender("男").motto("瓜兮兮~").build();String jsonStr = jacksonMapper.writeValueAsString(user);// 请求体数据类型任意,只要保证MediaType与其对应就行// 如: MediaType.parse("application/json; charset=utf-8");//     MediaType.parse("text/html; charset=utf-8");//     MediaType.parse("text/xml; charset=utf-8");//     ……RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, jsonStr);Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交表单*/@Testpublic void postTestTwo() throws IOException {// 地址必须要写上http/httpsString url = "http://127.0.0.1:8080/post/test/two";RequestBody formBody = new FormBody.Builder().add("platform", "android").add("name", "bug").add("subject", "XXXXXXXXXXXXXXX").build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(formBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交流*/@Testpublic void postTestThree() throws IOException {MediaType MEDIA_TYPE_HTML = MediaType.parse("text/html; charset=utf-8");// 地址必须要写上http/httpsString url = "http://127.0.0.1:8080/post/test/three";String streamData = "<b>我是流代表的数据~</b>";RequestBody streamBody = new RequestBody() {@Overridepublic MediaType contentType() {return MEDIA_TYPE_HTML;}@Overridepublic void writeTo(BufferedSink sink) throws IOException {sink.writeUtf8(streamData);}};Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(streamBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交文件*/@Testpublic void postTestFour() throws IOException {MediaType MEDIA_TYPE_FILE = MediaType.parse("multipart/form-data");// 地址必须要写上http/httpsString url = "http://127.0.0.1:8080/post/test/four";File fileOne = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\YAML.pdf");File fileTwo = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\abc.txt");MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)// 注意:服务端获取到的真实文件名,即为此处第二个参数的名字// 注意:表单的形式上传文件的话,需要key,第一个参数即为 对应的key.addFormDataPart("fileName", fileOne.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileOne)).addFormDataPart("fileName", fileTwo.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileTwo)).build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}}

notverifyca包下的HttpsDemo:

import com.aspire.okhttp.okhttpdemo.model.User;
import com.aspire.okhttp.okhttpdemo.util.OkHttpClientUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import okio.BufferedSink;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;/*** https示例(不校验服务端的身份, 即:不验证ca)** @author JustryDeng* @date 2019/6/6 23:37*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpsDemo {private static final Logger logger = LoggerFactory.getLogger(HttpsDemo.class);/** 使用jaskson作为json转换器 */private ObjectMapper jacksonMapper = new ObjectMapper();private static OkHttpClient client;static {try {client = OkHttpClientUtil.getHttpsClient();} catch (Exception e) {e.printStackTrace();logger.error("init OkHttpClient error!", e);}}/*** get-同步*/@Testpublic void getTestOne() throws IOException {// 地址必须要写上http/httpsString httpUrl = "https://127.0.0.1:9527/get/test?name=JustryDeng";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();// 响应体ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();/** 响应体的 string() 方法对于小文档来说十分方便、高效。* 但是如果响应体太大(超过1MB),应避免使用 string()方法 ,* 因为他会将把整个文档加载到内存中。对于超过1MB的响应body,* 应使用流的方式来处理body。*/// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** get-异步** 注:在一个工作线程中下载文件,当响应可读时回调Callback接口。读取*    响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。*/@Testpublic void getTestTwo() throws InterruptedException {// 地址必须要写上http/httpsString httpUrl = "https://127.0.0.1:9527/get/test?name=邓沙利文";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();//异步client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {logger.error("请求失败!请求相关信息为 -> {}!", call.request(), e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {String responseStr = response.body() == null ? null : response.body().string();logger.info("请求成功!响应Response为 -> {}!, 响应体数据为 -> {}!", response, responseStr);}});// 为了观察异步线程的日志输出, 这里不妨让当前线程休眠几秒TimeUnit.SECONDS.sleep(5);}/*** post-同步-提交请求体数据为json等类型的数据*/@Testpublic void postTestOne() throws IOException {MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/one";User user = User.builder().name("张三").age(12).gender("男").motto("瓜兮兮~").build();String jsonStr = jacksonMapper.writeValueAsString(user);// 请求体数据类型任意,只要保证MediaType与其对应就行// 如: MediaType.parse("application/json; charset=utf-8");//     MediaType.parse("text/html; charset=utf-8");//     MediaType.parse("text/xml; charset=utf-8");//     ……RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, jsonStr);Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交表单*/@Testpublic void postTestTwo() throws IOException {// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/two";RequestBody formBody = new FormBody.Builder().add("platform", "android").add("name", "bug").add("subject", "XXXXXXXXXXXXXXX").build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(formBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交流*/@Testpublic void postTestThree() throws IOException {MediaType MEDIA_TYPE_HTML = MediaType.parse("text/html; charset=utf-8");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/three";String streamData = "<b>我是流代表的数据~</b>";RequestBody streamBody = new RequestBody() {@Overridepublic MediaType contentType() {return MEDIA_TYPE_HTML;}@Overridepublic void writeTo(BufferedSink sink) throws IOException {sink.writeUtf8(streamData);}};Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(streamBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交文件*/@Testpublic void postTestFour() throws IOException {MediaType MEDIA_TYPE_FILE = MediaType.parse("multipart/form-data");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/four";File fileOne = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\YAML.pdf");File fileTwo = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\abc.txt");MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)// 注意:服务端获取到的真实文件名,即为此处第二个参数的名字// 注意:表单的形式上传文件的话,需要key,第一个参数即为 对应的key.addFormDataPart("fileName", fileOne.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileOne)).addFormDataPart("fileName", fileTwo.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileTwo)).build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}}

verifyca包下的HttpsDemo:

import com.aspire.okhttp.okhttpdemo.model.User;
import com.aspire.okhttp.okhttpdemo.util.OkHttpClientUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import okio.BufferedSink;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;/*** https示例(校验服务端的身份, 即:验证ca)** @author JustryDeng* @date 2019/6/6 23:37*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpsDemo {private static final Logger logger = LoggerFactory.getLogger(HttpsDemo.class);/** 使用jaskson作为json转换器 */private ObjectMapper jacksonMapper = new ObjectMapper();private static OkHttpClient client;static {InputStream ca = HttpsDemo.class.getClassLoader().getResourceAsStream("client/ds.crt");// 证书作为value,别名作为key; key只要保证唯一即可,但是最好是生成证书时 起的别名String alias = "dengshuai_ca";try {client = OkHttpClientUtil.getHttpsClient(ca, alias);} catch (Exception e) {e.printStackTrace();logger.error("init OkHttpClient error!", e);}}/*** get-同步*/@Testpublic void getTestOne() throws IOException {// 地址必须要写上http/httpsString httpUrl = "https://127.0.0.1:9527/get/test?name=JustryDeng";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();// 响应体ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();/** 响应体的 string() 方法对于小文档来说十分方便、高效。* 但是如果响应体太大(超过1MB),应避免使用 string()方法 ,* 因为他会将把整个文档加载到内存中。对于超过1MB的响应body,* 应使用流的方式来处理body。*/// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** get-异步** 注:在一个工作线程中下载文件,当响应可读时回调Callback接口。读取*    响应时会阻塞当前线程。OkHttp现阶段不提供异步api来接收响应体。*/@Testpublic void getTestTwo() throws InterruptedException {// 地址必须要写上http/httpsString httpUrl = "https://127.0.0.1:9527/get/test?name=邓沙利文";// 默认即为get请求Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(httpUrl).build();//异步client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {logger.error("请求失败!请求相关信息为 -> {}!", call.request(), e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {String responseStr = response.body() == null ? null : response.body().string();logger.info("请求成功!响应Response为 -> {}!, 响应体数据为 -> {}!", response, responseStr);}});// 为了观察异步线程的日志输出, 这里不妨让当前线程休眠几秒TimeUnit.SECONDS.sleep(5);}/*** post-同步-提交请求体数据为json等类型的数据*/@Testpublic void postTestOne() throws IOException {MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/one";User user = User.builder().name("张三").age(12).gender("男").motto("瓜兮兮~").build();String jsonStr = jacksonMapper.writeValueAsString(user);// 请求体数据类型任意,只要保证MediaType与其对应就行// 如: MediaType.parse("application/json; charset=utf-8");//     MediaType.parse("text/html; charset=utf-8");//     MediaType.parse("text/xml; charset=utf-8");//     ……RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, jsonStr);Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交表单*/@Testpublic void postTestTwo() throws IOException {// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/two";RequestBody formBody = new FormBody.Builder().add("platform", "android").add("name", "bug").add("subject", "XXXXXXXXXXXXXXX").build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(formBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交流*/@Testpublic void postTestThree() throws IOException {MediaType MEDIA_TYPE_HTML = MediaType.parse("text/html; charset=utf-8");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/three";String streamData = "<b>我是流代表的数据~</b>";RequestBody streamBody = new RequestBody() {@Overridepublic MediaType contentType() {return MEDIA_TYPE_HTML;}@Overridepublic void writeTo(BufferedSink sink) throws IOException {sink.writeUtf8(streamData);}};Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(streamBody).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}/*** post-同步-提交文件*/@Testpublic void postTestFour() throws IOException {MediaType MEDIA_TYPE_FILE = MediaType.parse("multipart/form-data");// 地址必须要写上http/httpsString url = "https://127.0.0.1:9527/post/test/four";File fileOne = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\YAML.pdf");File fileTwo = new File("C:\\Users\\JustryDeng\\Desktop\\abc\\abc.txt");MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)// 注意:服务端获取到的真实文件名,即为此处第二个参数的名字// 注意:表单的形式上传文件的话,需要key,第一个参数即为 对应的key.addFormDataPart("fileName", fileOne.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileOne)).addFormDataPart("fileName", fileTwo.getName(), RequestBody.create(MEDIA_TYPE_FILE, fileTwo)).build();Request request = new Request.Builder()// 注意: header添加的是 唯一的 键值对//      .addHeader添加的是 同key,多value的键值//      header相当于Map,addHeader相当于Multimap//.header("key1", "value1")//.addHeader("key2", "value2").url(url).post(body).build();Response response = client.newCall(request).execute();// 响应头// response有多个提取想用header的方法,下面列举的只是其一// response.headers();ResponseBody responseBody = response.body();// 将响应体转换为 字符串String responseStr = responseBody == null ? null : responseBody.string();// 将响应体转换为 流// responseBody.byteStream();logger.info("响应内容为 -> {}", responseStr);}}

注:上述对OkHttp的使用示例,只示例了常用的一部分。更多用法可详见OkHttp的官网,或自行查阅其他资料。

 

笔者吐槽及建议:

      如果要挨个挨个讲的话,太麻烦了,读者还是自己看代码吧!其实最快的方式还是直接去本人的Github上,将本人的测试项目下载下来,自己运行一下测试类

 

^_^ 如有不当之处,欢迎指正

^_^ 参考链接
              
              
              

^_^ 测试代码托管链接
              

^_^ 本文已经被收录进《程序员成长笔记(五)》,笔者JustryDeng

更多推荐

OkHttp发送HTTP、(验证/忽略CA的)HTTPS请求示例

本文发布于:2024-03-15 05:30:28,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1738201.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:示例   HTTP   OkHttp   HTTPS   CA

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!