https://github.com/square/okhttp
https://square.github.io/okhttp/
https://stackoverflow.com/questions/tagged/okhttp?sort=active
https://springboot.io/t/topic/154
关于 okhttp 版本问题
在项目中引入 okhttp 版本的选择很关键,因为 okhttp 版本 v4.0.0 是一个分水岭。
在 v4.0.0 之前的版本使用的是 Java 语言编写,在引入 pom 依赖时直接按照如下方式即可:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
但是自从 v4.0.0 开始,okhttp 使用 kotlin 语言进行了重写,所以在引入时防止编译出错需要引入相应的 **kotlin-stdlib**
:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin-stdlib.version}</version>
</dependency>
如果不引入 kotlin-stdlib
的话,在编译时可会遇到如下编译错误:
Exception in thread "main" java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B
at okio.Segment.writeTo(Segment.kt:169)
at okio.Segment.compact(Segment.kt:152)
at okio.Buffer.write(Buffer.kt:1842)
at okio.Buffer.read(Buffer.kt:1854)
at okio.Buffer.writeAll(Buffer.kt:1642)
at okio.Options$Companion.buildTrieRecursive(Options.kt:187)
at okio.Options$Companion.buildTrieRecursive(Options.kt:174)
at okio.Options$Companion.buildTrieRecursive$default(Options.kt:113)
at okio.Options$Companion.of(Options.kt:72)
at okhttp3.internal.Util.<clinit>(Util.kt:71)
at okhttp3.OkHttpClient.<clinit>(OkHttpClient.kt:1073)
at com.ituknown.okhttp.HttpClient.doPost(HttpClient.java:39)
at com.ituknown.okhttp.HttpClient.main(HttpClient.java:13)
FAQ:如何引入相应 okhttp 对应的 **kotlin-stdlib**
版本?
最简单地方式是先将 okhttp 引入到项目中,然后点击 okhttp 对应的 pom 就能看到对应的 kotlin-stdlib
版本了,示例:
创建 OkHttpClient 实例
构造 OkHttpClient 实例:
OkHttpClient okHttpClient = new OkHttpClient();
推荐使用下面的方式:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder().build();
因为在构造实例是我们通常会做些请求设置,比如超时时间、网络拦截器和DNS等。使用 Builder 模式更加方便,实例:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(3, TimeUnit.SECONDS) // 设置超时时间
.addInterceptor(...) // 添加拦截器
.dns(...) // 设置DNS
.build();
当然,使用第一种也是可以的。但是你不觉得 Builder 模式阅读起来更爽 :) ?
构造请求数据
OkHttpClient 在执行时,都是需要设置一个 okhttp3.Request 对象。这个 Request 主要用于设置请求类型、请求头、请求地址等等。
示例:
// GET 请求
Request request = new Request.Builder().url("...").addHeader("...", "...").get().build();
// POST 请求
Request request = new Request.Builder().url("...").addHeader("...", "...").post(...).build();
// DELETE 请求
Request request = new Request.Builder().url("...").addHeader("...", "...").delete().build();
// PUT 请求
Request request = new Request.Builder().url("...").addHeader("...", "...").put(...).build();
之后使用 OkHttpClient 实例执行即可:
Response response = okHttpClient.newCall(request).execute();
GET 请求
GET 请求比较简单,只需要创建一个 Request 对象即可,实例:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://localhost:8080/user?id=1")
.addHeader("Accept", "application/json")
.get()
.build();
// 同步执行
Response response = okHttpClient.newCall(request).execute();
int code = response.code(); // 状态码
Protocol protocol = response.protocol(); // 请求协议
ResponseBody body = response.body(); // 响应体对象
String data = body.string(); // 响应数据
注意响应数据是 **body.string()**
而不是 **body.toString()**
!!!!
POST 请求
POST 请求相对于 GET 请求唯一多的一步就是构造请求体数据:
Request request = new Request.Builder()
.url("http://localhost:8080/")
.addHeader("Accept", "application/json")
.post(RequestBody) // 注意这里
.build();
okhttp3.RequestBody 类内部提供了多种静态方法,用于创建请求体数据的形式:
public static RequestBody create(final MediaType contentType, final byte[] content);
public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount);
public static RequestBody create(final MediaType contentType, final ByteString content);
public static RequestBody create(final MediaType contentType, final byte[] content);
public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount);
public static RequestBody create(final MediaType contentType, final File file);
其中 okhttp3.MediaType 对象是用于设置请求体类型,比如我想要请求的是 JSON 格式请求体就可以使用如下形式:
String jsonTxt = "...";
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), jsonTxt);
之后设置到 okhttp3.Request 对象即可:
Request request = new Request.Builder()
.url("http://localhost:8080/")
.addHeader("Accept", "application/json")
.post(requestBody) // 注意这里
.build();
这些静态方法基本上满足我们日常使用了。不过,okhttp 还提供了两个 okhttp3.RequestBody 的扩展类,分别是用于文件上传的 **okhttp3.MultipartBody**
和 Form 表单数据 **okhttp3.FormBody**
:
JSON 请求
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
User user = new User();
user.setUsername("HanMeimei");
user.setAge(18);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user);
// 创建 JSON 请求体数据
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), json);
Request request = new Request.Builder()
.url("http://localhost:8080/")
.addHeader("Accept", "application/json")
.post(requestBody) // 请求体数据
.build();
// 同步执行
Response response = okHttpClient.newCall(request).execute();
int code = response.code(); // 状态码
Protocol protocol = response.protocol(); // 请求协议
ResponseBody body = response.body(); // 响应体对象
String data = body.string(); // 响应数据
表单请求
Form 表单请求使用的 Media 类型是 application/x-www-form-urlencoded
。okhttp 提供了一个对应的 Form 表单请求体类:FormBody
。我们可以直接使用该对象构造 Form 表单数据即可:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
FormBody requestBody = new FormBody.Builder()
.add("username", "HanMeimei")
.addEncoded("charset", "UTF-8")
.build();
Request request = new Request.Builder()
.url("http://localhost:8080/")
.addHeader("Accept", "application/json")
.post(requestBody)
.build();
// 同步执行
Response response = okHttpClient.newCall(request).execute();
int code = response.code(); // 状态码
Protocol protocol = response.protocol(); // 请求协议
ResponseBody body = response.body(); // 响应体对象
String data = body.string(); // 响应数据
需要说明的是,FormBody
对象内部设置了一个默认 MediaType:
private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
所以我们在发送请求是无需手动设置 MediaType。
文件上传
okhttp 同样提供了一个用于构造文件上传的请求体类:MultipartBody
。我们可以直接借助该类构造文件上传对象,示例如下:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
// 文件
File file = new File("/path/RequestBody.png");
String fileName = file.getName();
MultipartBody multipartBody = new MultipartBody.Builder()
// 后台接收的 key, 文件名称, 文件对象
.addFormDataPart("file", fileName, RequestBody.create(MediaType.parse("image/png"), file))
.build();
Request request = new Request.Builder()
.url("http://localhost:8080/upload")
.addHeader("Accept", "application/json")
.post(multipartBody)
.build();
// 同步执行
Response response = okHttpClient.newCall(request).execute();
// 同步执行
Response response = okHttpClient.newCall(request).execute();
int code = response.code(); // 状态码
Protocol protocol = response.protocol(); // 请求协议
ResponseBody body = response.body(); // 响应体对象
String data = body.string(); // 响应数据
看下后台接收的文件上传示例:
另外,在上面的示例中我没有设置 MediaTye。实际上在 MultipartBody
类内部定义了多个 MediaType 可供我们使用:
public static final MediaType MIXED = MediaType.get("multipart/mixed");
public static final MediaType ALTERNATIVE = MediaType.get("multipart/alternative");
public static final MediaType DIGEST = MediaType.get("multipart/digest");
public static final MediaType PARALLEL = MediaType.get("multipart/parallel");
public static final MediaType FORM = MediaType.get("multipart/form-data");
比如我在文件上传时指定以 multipart/form-data
形式进行上传:
MultipartBody multipartBody = new MultipartBody.Builder()
// 设置 MediaType
.setType(MultipartBody.FORM)
// 后台接收的 key, 文件名称, 文件对象
.addFormDataPart("file", fileName, RequestBody.create(MediaType.parse("image/png"), file))
.build();
文件下载
文件下载就是普通的 GET 请求,唯一的区别就是注意响应数据的处理。进行文件下载时,我们应该获取响应数据的二进制流数据而不是文本字符串,然后再根据具体的媒体进行进行响应的处理即可:
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.readTimeout(1, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://localhost:8080/head?id=1")
.addHeader("Accept", "application/json")
.get()
.build();
int code = response.code(); // 状态码
Protocol protocol = response.protocol(); // 请求协议
ResponseBody body = response.body(); // 响应体对象
// 注意这里
InputStream inputStream = body.byteStream(); // 二进制流数据
MediaType mediaType = body.contentType(); // 二进制流的媒体类型
异步请求
上面的示例都是同步请求,即如下形式:
Response response = okHttpClient.newCall(request).execute();