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 依赖时直接按照如下方式即可:

  1. <dependency>
  2. <groupId>com.squareup.okhttp3</groupId>
  3. <artifactId>okhttp</artifactId>
  4. <version>${okhttp.version}</version>
  5. </dependency>

但是自从 v4.0.0 开始,okhttp 使用 kotlin 语言进行了重写,所以在引入时防止编译出错需要引入相应的 **kotlin-stdlib**

  1. <dependency>
  2. <groupId>com.squareup.okhttp3</groupId>
  3. <artifactId>okhttp</artifactId>
  4. <version>${okhttp.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.jetbrains.kotlin</groupId>
  8. <artifactId>kotlin-stdlib</artifactId>
  9. <version>${kotlin-stdlib.version}</version>
  10. </dependency>

如果不引入 kotlin-stdlib 的话,在编译时可会遇到如下编译错误:

  1. Exception in thread "main" java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B
  2. at okio.Segment.writeTo(Segment.kt:169)
  3. at okio.Segment.compact(Segment.kt:152)
  4. at okio.Buffer.write(Buffer.kt:1842)
  5. at okio.Buffer.read(Buffer.kt:1854)
  6. at okio.Buffer.writeAll(Buffer.kt:1642)
  7. at okio.Options$Companion.buildTrieRecursive(Options.kt:187)
  8. at okio.Options$Companion.buildTrieRecursive(Options.kt:174)
  9. at okio.Options$Companion.buildTrieRecursive$default(Options.kt:113)
  10. at okio.Options$Companion.of(Options.kt:72)
  11. at okhttp3.internal.Util.<clinit>(Util.kt:71)
  12. at okhttp3.OkHttpClient.<clinit>(OkHttpClient.kt:1073)
  13. at com.ituknown.okhttp.HttpClient.doPost(HttpClient.java:39)
  14. at com.ituknown.okhttp.HttpClient.main(HttpClient.java:13)

FAQ:如何引入相应 okhttp 对应的 **kotlin-stdlib** 版本?

最简单地方式是先将 okhttp 引入到项目中,然后点击 okhttp 对应的 pom 就能看到对应的 kotlin-stdlib 版本了,示例:
Screen Shot 2021-10-20 at 16.24.58.png
Screen Shot 2021-10-20 at 16.27.18.png

创建 OkHttpClient 实例

构造 OkHttpClient 实例:

  1. OkHttpClient okHttpClient = new OkHttpClient();

推荐使用下面的方式:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder().build();

因为在构造实例是我们通常会做些请求设置,比如超时时间、网络拦截器和DNS等。使用 Builder 模式更加方便,实例:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .connectTimeout(3, TimeUnit.SECONDS) // 设置超时时间
  3. .addInterceptor(...) // 添加拦截器
  4. .dns(...) // 设置DNS
  5. .build();

当然,使用第一种也是可以的。但是你不觉得 Builder 模式阅读起来更爽 :) ?

构造请求数据

OkHttpClient 在执行时,都是需要设置一个 okhttp3.Request 对象。这个 Request 主要用于设置请求类型、请求头、请求地址等等。

示例:

  1. // GET 请求
  2. Request request = new Request.Builder().url("...").addHeader("...", "...").get().build();
  3. // POST 请求
  4. Request request = new Request.Builder().url("...").addHeader("...", "...").post(...).build();
  5. // DELETE 请求
  6. Request request = new Request.Builder().url("...").addHeader("...", "...").delete().build();
  7. // PUT 请求
  8. Request request = new Request.Builder().url("...").addHeader("...", "...").put(...).build();

之后使用 OkHttpClient 实例执行即可:

  1. Response response = okHttpClient.newCall(request).execute();

GET 请求

GET 请求比较简单,只需要创建一个 Request 对象即可,实例:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .readTimeout(1, TimeUnit.SECONDS)
  3. .build();
  4. Request request = new Request.Builder()
  5. .url("http://localhost:8080/user?id=1")
  6. .addHeader("Accept", "application/json")
  7. .get()
  8. .build();
  9. // 同步执行
  10. Response response = okHttpClient.newCall(request).execute();
  11. int code = response.code(); // 状态码
  12. Protocol protocol = response.protocol(); // 请求协议
  13. ResponseBody body = response.body(); // 响应体对象
  14. String data = body.string(); // 响应数据

注意响应数据是 **body.string()** 而不是 **body.toString()** !!!!

POST 请求

POST 请求相对于 GET 请求唯一多的一步就是构造请求体数据:

  1. Request request = new Request.Builder()
  2. .url("http://localhost:8080/")
  3. .addHeader("Accept", "application/json")
  4. .post(RequestBody) // 注意这里
  5. .build();

okhttp3.RequestBody 类内部提供了多种静态方法,用于创建请求体数据的形式:

  1. public static RequestBody create(final MediaType contentType, final byte[] content);
  2. public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount);
  3. public static RequestBody create(final MediaType contentType, final ByteString content);
  4. public static RequestBody create(final MediaType contentType, final byte[] content);
  5. public static RequestBody create(final MediaType contentType, final byte[] content, final int offset, final int byteCount);
  6. public static RequestBody create(final MediaType contentType, final File file);

其中 okhttp3.MediaType 对象是用于设置请求体类型,比如我想要请求的是 JSON 格式请求体就可以使用如下形式:

  1. String jsonTxt = "...";
  2. RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), jsonTxt);

之后设置到 okhttp3.Request 对象即可:

  1. Request request = new Request.Builder()
  2. .url("http://localhost:8080/")
  3. .addHeader("Accept", "application/json")
  4. .post(requestBody) // 注意这里
  5. .build();

这些静态方法基本上满足我们日常使用了。不过,okhttp 还提供了两个 okhttp3.RequestBody 的扩展类,分别是用于文件上传的 **okhttp3.MultipartBody** 和 Form 表单数据 **okhttp3.FormBody**

RequestBody.png

JSON 请求

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .readTimeout(1, TimeUnit.SECONDS)
  3. .build();
  4. User user = new User();
  5. user.setUsername("HanMeimei");
  6. user.setAge(18);
  7. ObjectMapper objectMapper = new ObjectMapper();
  8. String json = objectMapper.writeValueAsString(user);
  9. // 创建 JSON 请求体数据
  10. RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), json);
  11. Request request = new Request.Builder()
  12. .url("http://localhost:8080/")
  13. .addHeader("Accept", "application/json")
  14. .post(requestBody) // 请求体数据
  15. .build();
  16. // 同步执行
  17. Response response = okHttpClient.newCall(request).execute();
  18. int code = response.code(); // 状态码
  19. Protocol protocol = response.protocol(); // 请求协议
  20. ResponseBody body = response.body(); // 响应体对象
  21. String data = body.string(); // 响应数据

表单请求

Form 表单请求使用的 Media 类型是 application/x-www-form-urlencoded。okhttp 提供了一个对应的 Form 表单请求体类:FormBody。我们可以直接使用该对象构造 Form 表单数据即可:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .readTimeout(1, TimeUnit.SECONDS)
  3. .build();
  4. FormBody requestBody = new FormBody.Builder()
  5. .add("username", "HanMeimei")
  6. .addEncoded("charset", "UTF-8")
  7. .build();
  8. Request request = new Request.Builder()
  9. .url("http://localhost:8080/")
  10. .addHeader("Accept", "application/json")
  11. .post(requestBody)
  12. .build();
  13. // 同步执行
  14. Response response = okHttpClient.newCall(request).execute();
  15. int code = response.code(); // 状态码
  16. Protocol protocol = response.protocol(); // 请求协议
  17. ResponseBody body = response.body(); // 响应体对象
  18. String data = body.string(); // 响应数据

需要说明的是,FormBody 对象内部设置了一个默认 MediaType:

  1. private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");

所以我们在发送请求是无需手动设置 MediaType。

文件上传

okhttp 同样提供了一个用于构造文件上传的请求体类:MultipartBody。我们可以直接借助该类构造文件上传对象,示例如下:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .readTimeout(1, TimeUnit.SECONDS)
  3. .build();
  4. // 文件
  5. File file = new File("/path/RequestBody.png");
  6. String fileName = file.getName();
  7. MultipartBody multipartBody = new MultipartBody.Builder()
  8. // 后台接收的 key, 文件名称, 文件对象
  9. .addFormDataPart("file", fileName, RequestBody.create(MediaType.parse("image/png"), file))
  10. .build();
  11. Request request = new Request.Builder()
  12. .url("http://localhost:8080/upload")
  13. .addHeader("Accept", "application/json")
  14. .post(multipartBody)
  15. .build();
  16. // 同步执行
  17. Response response = okHttpClient.newCall(request).execute();
  18. // 同步执行
  19. Response response = okHttpClient.newCall(request).execute();
  20. int code = response.code(); // 状态码
  21. Protocol protocol = response.protocol(); // 请求协议
  22. ResponseBody body = response.body(); // 响应体对象
  23. String data = body.string(); // 响应数据

看下后台接收的文件上传示例:
Screen Shot 2021-10-20 at 17.33.32.png
另外,在上面的示例中我没有设置 MediaTye。实际上在 MultipartBody 类内部定义了多个 MediaType 可供我们使用:

  1. public static final MediaType MIXED = MediaType.get("multipart/mixed");
  2. public static final MediaType ALTERNATIVE = MediaType.get("multipart/alternative");
  3. public static final MediaType DIGEST = MediaType.get("multipart/digest");
  4. public static final MediaType PARALLEL = MediaType.get("multipart/parallel");
  5. public static final MediaType FORM = MediaType.get("multipart/form-data");

比如我在文件上传时指定以 multipart/form-data 形式进行上传:

  1. MultipartBody multipartBody = new MultipartBody.Builder()
  2. // 设置 MediaType
  3. .setType(MultipartBody.FORM)
  4. // 后台接收的 key, 文件名称, 文件对象
  5. .addFormDataPart("file", fileName, RequestBody.create(MediaType.parse("image/png"), file))
  6. .build();

文件下载

文件下载就是普通的 GET 请求,唯一的区别就是注意响应数据的处理进行文件下载时,我们应该获取响应数据的二进制流数据而不是文本字符串,然后再根据具体的媒体进行进行响应的处理即可:

  1. OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
  2. .readTimeout(1, TimeUnit.SECONDS)
  3. .build();
  4. Request request = new Request.Builder()
  5. .url("http://localhost:8080/head?id=1")
  6. .addHeader("Accept", "application/json")
  7. .get()
  8. .build();
  9. int code = response.code(); // 状态码
  10. Protocol protocol = response.protocol(); // 请求协议
  11. ResponseBody body = response.body(); // 响应体对象
  12. // 注意这里
  13. InputStream inputStream = body.byteStream(); // 二进制流数据
  14. MediaType mediaType = body.contentType(); // 二进制流的媒体类型

异步请求

上面的示例都是同步请求,即如下形式:

  1. Response response = okHttpClient.newCall(request).execute();