okHttp 初体验

本贴最后更新于 2468 天前,其中的信息可能已经沧海桑田

背景

之前请求 http 服务器,一直在使用 httpClient 库,最近发现 android 开发中广泛使用的为 okHttp 库。

本文体验一下 okHttp 库。

介绍

okHttp 库是一个 HTTP 客户端,在 Android 开发中广泛使用,特点为:

  • 支持 HTTP/2,允许对一个站点的所有请求共享一个 socket。
  • 如果服务端不支持 HTTP/2,则使用连接池,来降低请求延时。
  • 透明使用 GZIP,来减少下载字节。
  • 缓存响应结果,对于重复请求直接读缓存,不再走网络请求。

使用

pom 依赖

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

同步调用

package testOkHttp; import java.io.IOException; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class MainOkHttp { public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient(); // GET示例 { Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip") .addHeader("User-Agent", "mozilla").build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } // POST示例 { String url = "http://ip.taobao.com/service/getIpInfo2.php"; String content = "ip=myip"; RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), content); Request request = new Request.Builder().url(url).post(body).build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } } }

异步调用

异步发起的请求会被加入到 Dispatcher 中的 runningAsyncCalls 双端队列中通过线程池来执行。不会阻塞主线程。

可以设置每个 webserver 的最大并发数(默认为 5), 以及全局最大并发数(默认为 64)

package testOkHttp; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class MainOkHttp2 { public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient(); // 异步GET请求示例 { Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip") .addHeader("User-Agent", "mozilla").build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println("onResponse: " + response.body().string()); } }); } // 异步POST请求示例 { String url = "http://ip.taobao.com/service/getIpInfo2.php"; String content = "ip=myip"; RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), content); Request request = new Request.Builder().url(url).post(body).build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println("onResponse: " + response.body().string()); } }); } } }

本文到处即可以结束的。

但是 okHttp 还有一些高级用法,不妨再继续看看。

Post 方法

RequestBody 有多种构造方法

数据流

不断的写入 sink 即可。

RequestBody body = new RequestBody() { @Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("ip=myip"); } @Override public MediaType contentType() { return MediaType.parse("application/x-www-form-urlencoded"); } };

提交文件

会自动从文件中把内容读出来

File file = new File("/tmp/ip.txt"); RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), file);

提交表单

RequestBody body = new FormBody.Builder().add("ip", "myip").build();

提交分块请求

// 使用imgur图片上传api为例. client-Id需要自己申请. // https://api.imgur.com/endpoints/image MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) .addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""), RequestBody.create(null, "abeffect test")) .addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""), RequestBody.create(MediaType.parse("image/jpg"), new File("/tmp/1.jpg"))) .build(); Request request = new Request.Builder().header("Authorization", "Client-ID your-own-client-id") .url("https://api.imgur.com/3/image").post(body).build();

完整代码

package testOkHttp.post; import java.io.File; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class PostMultipartBody { public static void main(String[] args) { OkHttpClient client = new OkHttpClient(); // 异步POST请求示例 { // 使用imgur图片上传api为例. client-Id需要自己申请. // https://api.imgur.com/endpoints/image MultipartBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) .addPart(Headers.of("Content-Disposition", "form-data; name=\"title\""), RequestBody.create(null, "abeffect test")) .addPart(Headers.of("Content-Disposition", "form-data; name=\"image\""), RequestBody.create(MediaType.parse("image/jpg"), new File("/tmp/1.jpg"))) .build(); Request request = new Request.Builder().header("Authorization", "Client-ID your-own-client-id") .url("https://api.imgur.com/3/image").post(body).build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("onFailure: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println("onResponse: " + response.body().string()); } }); } } }

拦截器(Interceptors)

图片来自[2]
imagepng

拦截器,就是在调用前后可以插入自己的代码。okHttp 可以在 Application 请求 OkHttp 前后进行拦截,也可以 OkHttp 请求 Network 前后进行拦截。

应用拦截器

应用拦截器,在请求 OkHttp core 时进行拦截。

package testOkHttp; import java.io.IOException; import java.util.logging.Logger; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class ApplicationInterceptors { private static final Logger logger = Logger.getLogger(ApplicationInterceptors.class.getName()); static class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build(); Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip") .addHeader("User-Agent", "mozilla").build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } }

运行结果

Jul 23, 2018 2:56:45 PM testOkHttp.ApplicationInterceptors$LoggingInterceptor intercept 信息: Sending request http://ip.taobao.com/service/getIpInfo2.php?ip=myip on null User-Agent: mozilla Jul 23, 2018 2:56:45 PM testOkHttp.ApplicationInterceptors$LoggingInterceptor intercept 信息: Received response for http://ip.taobao.com/service/getIpInfo2.php?ip=myip in 165.0ms Date: Mon, 23 Jul 2018 06:56:45 GMT Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding {"code":0,"data":{"ip":"182.92.253.3","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"阿里巴巴","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100098"}}

网络拦截器

除了执行时的调用位置不同,其它和应用拦截器是一样的。

package testOkHttp; import java.io.IOException; import java.util.logging.Logger; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class NetworkInterceptors { private static final Logger logger = Logger.getLogger(NetworkInterceptors.class.getName()); static class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } } public static void main(String[] args) throws IOException { OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new LoggingInterceptor()).build(); Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo2.php?ip=myip") .addHeader("User-Agent", "mozilla").build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } }

执行结果

Jul 23, 2018 3:09:33 PM testOkHttp.NetworkInterceptors$LoggingInterceptor intercept 信息: Sending request http://ip.taobao.com/service/getIpInfo2.php?ip=myip on Connection{ip.taobao.com:80, proxy=DIRECT hostAddress=ip.taobao.com/106.14.52.115:80 cipherSuite=none protocol=http/1.1} User-Agent: mozilla Host: ip.taobao.com Connection: Keep-Alive Accept-Encoding: gzip Jul 23, 2018 3:09:33 PM testOkHttp.NetworkInterceptors$LoggingInterceptor intercept 信息: Received response for http://ip.taobao.com/service/getIpInfo2.php?ip=myip in 54.6ms Date: Mon, 23 Jul 2018 07:09:32 GMT Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Content-Encoding: gzip {"code":0,"data":{"ip":"182.92.253.3","country":"中国","area":"","region":"北京","city":"北京","county":"XX","isp":"阿里巴巴","country_id":"CN","area_id":"","region_id":"110000","city_id":"110100","county_id":"xx","isp_id":"100098"}}

内置拦截器

内置拦截器有下面这些,这里同时附上两个自定义拦截器,按执行顺序排列好:

  • client.interceptors(): 自定义的应用拦截器
  • RetryAndFollowUpInterceptor: 跟踪重定向,用户验证和错误等, 默认最多 20 个.
  • BridgeInterceptor: 桥接应用代码和网络代码, 添加必要的头信息,处理 gzip 等
  • CacheInterceptor: 维护请求的缓存
  • ConnectInterceptor: 向 server 建立连接
  • client.networkInterceptors(): 自定义的网络拦截器
  • CallServerInterceptor: 链中最后一个拦截器,向 server 发送请求

参考

  1. OkHttp
  2. OkHttp Wiki
  3. Okhttp3 基本使用
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 84 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3198 引用 • 8215 回帖 • 1 关注

相关帖子

6 回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • wizardforcel

    看起来像是照搬 Apache Common 的设计。。

  • 其他回帖
  • someone

    大神

  • someone

    JDK HTTP Client 看起来也不错,改天调研一下😄

  • springmarker

    java11 出来不知道自带的 HttpClient 好不好用

    1 回复
  • 查看全部回帖