okHttp 初体验
背景
之前请求 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)
拦截器,就是在调用前后可以插入自己的代码。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 发送请求