基本概念
相关文档:https://github.com/square/okhttp
PS:基于okhttp库实现,OkHttpClient中实现了get 、 post客户端请求方法。
OkHttp是一个高效、灵活、易于使用的HTTP客户端库。在网络请求处理上采用了异步模型,并将连接池、压缩、网络协议等多种技术应用到其中,从而提高了网络请求的效率和处理速度。OkHttp支持HTTP/2协议,可以进行数据流复用以及服务器推送。同时,OkHttp还支持GZIP压缩、连接超时设置、缓存、重试等功能,提供了非常丰富的API接口
引入pom.xml依赖:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
Java.net URL & URI区别:
URL url = new URL("http://localhost:80");
System.out.println(url.getHost()+" , "+ url.getPort() +" , " +url.getProtocol());//localhost , 80 , http
URI uri = URI.create("http://localhost:80");
System.out.println(uri.getHost() +" , "+ uri.getPort() );//localhost , 80
1. 实例化OkHttpClient
//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
OkHttpClient okHttpClient = new OkHttpClient();
//方式二:使用静态内部类
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
//方式三:使用实例方法newBuilder(), 其实也是使用的静态内部类
OkHttpClient client = new OkHttpClient().newBuilder().build();
1-2.超时、连接池、缓存等OkHttpClient实例初始化参数设置
private static TrustManager[] buildTrustManagers() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) { }
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) { }
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
}
/** 生成安全套接字工厂,用于https请求的证书跳过 */
private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
SSLSocketFactory ssfFactory = null;
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
ssfFactory = sc.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
return ssfFactory;
}
TrustManager[] trustManagers = buildTrustManagers();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) //超时
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
// 跳过ssl认证(https) ,下面hostname校验也需要跳过
.sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
.hostnameVerifier((hostName, session) -> true) //跳过hostname校验.
.retryOnConnectionFailure(true)
//设置连接池最大连接数量:持续存活的连接。最大连接数、连接存活的时间、连接存活的时间单位
.connectionPool(new ConnectionPool(10, 10, TimeUnit.MINUTES))
// 参数1:缓存文件 参数2:缓存大小
//.cache(new Cache(new File("/home/work/cache.tmp"), 10 * 1024 * 1024))
.build();
2-1.同步发送get请求
try {
OkHttpClient client = new OkHttpClient();
//HttpUrl.Builder builder = HttpUrl.get(url).newBuilder();
//builder.addQueryParameter((String)key, (String) value);
//HttpUrl url = builder.build(); //也可以通过api方式添加queryParameter参数生成url
Request request = new Request.Builder().url("https://www.baidu.com?a=a").get().build();
Response response = client.newCall(request).execute();
// 响应状态码
int code = response.code();
// 响应body
ResponseBody body = response.body();
// 响应body1: 字符串, 当响应内容比较小的时候使用, 响应内容太大最好使用后面的方式, 因为该方法会将内容全部加载到内容
String str = body.string();
// 响应body2: 字节数组
byte[] bytes = body.bytes();
// 响应body3: 字符流
Reader reader = body.charStream();
// 响应body4: 字节流
InputStream stream = body.byteStream();
} catch (IOException e) {
e.printStackTrace();
}
2-2. 异步发送get请求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://www.baidu.com").get().build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) { }
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body().string();
System.out.println(result);
}
});
3.发送post请求
3-1. 参数为类型为 application/json ( RequestBody )
try {
OkHttpClient client = new OkHttpClient();
/**
* 构建RequestBody常用的三种方法
* public static RequestBody create(@Nullable MediaType contentType, String content)
* public static RequestBody create(@Nullable MediaType contentType, byte[] content)
* public static RequestBody create(@Nullable final MediaType contentType, final File file)
*/
// 构建请求参数
HashMap<String, String> param = new HashMap<>();
param.put("name", "zhangsan");
// 设置参数类型
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(mediaType, JSON.toJSONString(param));
Request request = new Request.Builder().url("https://xxxx").post(requestBody).build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
3-2.参数类型为application/x-www-form-urlencoded 普通表单(FormBody)
try {
OkHttpClient client = new OkHttpClient();
// 构建表单body
FormBody.Builder builder = new FormBody.Builder();
builder.add("name", "zhangsan");
FormBody formBody = builder.build();
Request request = new Request.Builder().url("https://xxxx").post(formBody).build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
3-3.参数类型为multipart/form-data ,数据里有文件(MultipartBody)
try {
OkHttpClient client = new OkHttpClient();
// 构建带文件的body
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
// 键值参数
builder.addFormDataPart("name", "zhangsan");
// 文件, 二进制流数据 application/octet-stream, 不关注文件的扩展类型
RequestBody requestBody =
RequestBody.create(MediaType.parse("application/octet-stream"), new File("/home/work/1.txt")); //new byte[]{1, 2, 3}; 也可以是将文件内容读取转成byte[]
builder.addFormDataPart("file", "1.txt", requestBody);
MultipartBody multipartBody = builder.build();
Request request = new Request.Builder().url("https://xxxx").post(multipartBody).build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
5.authenticator自动认证修正验证器
authenticator 401(Unauthorized)服务器失效认证
authenticator自动认证修正验证器。比如 token 失效的时候返回 401(Unauthorized),在这里重新获取刷新 token。
当 HTTP 响应的状态代码是 401 时,OkHttp 会从设置的 authenticator 对象中获取到新的 Request 对象并再次尝试发出请求。Authenticator 接口中的 authenticate 方法用来提供进行认证的 Request 对象。
Authorization的授权认证方式在我接触中有两种Basic、Bearer
(1)Basic:HTTP基本认证,在请求的时候加上以下请求头:
Authorization : basic base64encode(username+":"+password))
将用户名和密码用英文冒号(:)拼接起来,并进行一次Base64编码。服务端拿到basic码,然后自己查询相关信息再按照base64encode(username+":"+password))的方式得出当前用户的basic进行对比。
(2)Bearer: 就是常用的token验证。在使用的时候需要加上以下请求头:
Authorization: Bearer xxxxx
双token机制 refresh_token(刷新token,时间一般较长) 和 access_token(认证token时间一般较短)可保证,用户操作过程中避免token过期。
Authenticator返回null的时候,OkHttp会停止重试。为了避免Authentication不起效时还反复重试, 可以再重试过一次之后,返回null退出执行
try {
OkHttpClient okHttpClient =new OkHttpClient.Builder()
.authenticator(new Authenticator() {//401(Unauthorized)无权限触发重新鉴权并构建新request重试
public Request authenticate(Route route, Response response) throws IOException {
System.out.println(response.challenges().toString());
String credential = Credentials.basic("jesse", "password1");//重新doLogin授权认证
if (responseCount(response) >= 1) {//重新尝试调用此接口一次后,放弃尝试
return null;//返回null则停止重试
}
//返回一个request对象则okhttp使用该request对象再次尝试发出请求
return response.request().newBuilder().addHeader("Authorization", credential).build();
}
}).build();
Request request = new Request.Builder().url("http://xxxx").build();
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
System.out.println(response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
}
private int responseCount(Response response) {
int result = 0;
while ((response = response.priorResponse()) != null) { //如果此响应不是由自动重试触发的,则返回null
result++;
}
return result;
}
自动触发authenticator原理:
1.通过Response对象的challenges()方法可以得到第一次请求失败的授权相关的信息.
Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets"]
2.如果响应码是401 unauthorized,那么会返回"WWW-Authenticate"相关信息,这种情况下,要执行OkHttpClient.Builder的authenticator()方法在Authenticator对象的authenticate()中 对新的Request对象调用header("Authorization", credential)方法,设置其Authorization请求头;
3.如果Response的响应码是407 proxy unauthorized,那么会返回"Proxy-Authenticate"相关信息,表示不是最终的服务器要求客户端登录授权信息,而是客户端和服务器之间的代理服务器要求客户端登录授权信息,这时候要执行OkHttpClient.Builder的proxyAuthenticator()方法,在Authenticator对象的authenticate()中 对新的Request对象调用header("Proxy-Authorization", credential)方法,设置其Proxy-Authorization请求头。
proxyAuthenticator 407(Proxy Authentication Required)针对代理服务器的认证
authenticateProxy 方法用来提供对代理服务器进行认证的 Request 对象。
当 HTTP 响应的状态代码返回 407 时,OkHttp 会从设置的 authenticateProxy 对象中获取到新的 Request 对象并再次尝试发出请求。Authenticator 接口中的 authenticate 方法用来提供进行认证的 Request 对象。
OkHttpClient okHttpClient =new OkHttpClient.Builder()
.proxyAuthenticator(new Authenticator() {//407(roxy Authentication Required)无权限触发重新鉴权并构建新request重试
public Request authenticate(Route route, Response response) throws IOException {
System.out.println(response.challenges().toString());
String credential = Credentials.basic("jesse", "password1");//重新doLogin授权认证
if (responseCount(response) >= 1) {//重新尝试调用此接口一次后,放弃尝试
return null;//返回null则停止重试
}
//返回一个request对象则okhttp使用该request对象再次尝试发出请求
return response.request().newBuilder().addHeader("Authorization", credential).build();
}
}).build();
Request request = new Request.Builder().url("http://xxxx").build();
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
System.out.println(response.body().string());
}
6. 自动存储和携带cookie
static Map<String, List<Cookie>> cookieStore = new HashMap<>();
try {
OkHttpClient okHttpClient =
new OkHttpClient
.Builder()
.cookieJar(new CookieJar() {
//当返回响应时,okHttp通过saveFromResponse 方法解析响应报文的setCookie字段,获取cookie相关信息存入map
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
cookieStore.put(httpUrl.host(), list);
}
//当网络请求时okHttp通过loadForRequest方法返回一个List 并会解析到下一次请求报文的cookie首部,okhttp最终是以 builder.addHeader("Cookie", cookie)的方式添加到请求头中
//目前做得不是很严格,若要和浏览器特性一致,这里还需要做domain 和 path校验过滤一下(子路径 子域名可添加进去)
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl) {
List<Cookie> cookies = cookieStore.get(httpUrl.host());
return cookies != null ? cookies : Collections.emptyList();
}
}).build();
Request request = new Request.Builder().url("http://xxxx").build();
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
System.out.println(response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
}
CookieJar底层源码:
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
7.为request添加请求头
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();