OkHttp是一个高效的HTTP客户端,他有如下特性:
当网络出现问题时,OkHttp会自动恢复一般的连接问题;若服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP。
OkHttp采用流式构造接口,方便调用;同时支持同步与异步方式调用。
要使用OkHttp,需要先在pom.xml中引入依赖包;okhttp3是使用kotlin实现的,所以对应包也需要引入:
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.9.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib --> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.5.0</version> </dependency>
通过共享的响应缓存/线程池/复用的连接等,绝大多数应用只需一个OkHttpClient实例,便可以满足整个应用的所有Http请求。
OkHttpClient client = new OkHttpClient(); OkHttpClient httpClient = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) // default 10s .writeTimeout(30, TimeUnit.SECONDS) // default 10s .readTimeout(30, TimeUnit.SECONDS) // default 10s .build(); OkHttpClient anotherClient = client.newBuilder().build();
通过HttpUrl.Builder可方便地构造Url:
public static HttpUrl buildUrl(String url, Map<String, String> queryParam) { HttpUrl.Builder builder = HttpUrl.parse(url).newBuilder(); queryParam.forEach((k, v) -> { builder.addQueryParameter(k, v); }); return builder.build(); }
构造完整Url的流程:
HttpUrl.Builder builder = new HttpUrl.Builder() .host("127.0.0.1") .port(8001) .addPathSegment("seg1") .addPathSegment("path2") .username("user") .password("pass") .scheme("https") .addQueryParameter("k1", "v1") .addQueryParameter("k1", "v2") .setQueryParameter("uk", "v1") .setQueryParameter("uk", "v2"); HttpUrl http = builder.build(); System.out.println(http.toString()); // https://user:pass@127.0.0.1:8001/seg1/path2?k1=v1&k1=v2&uk=v2
HTTP头(可参见请求头大全)是 Map<String, List<String>>
类型。也就是说,对于每个 HTTP 头,可能有多个值;但是大部分 HTTP 头都只有一个值。
OkHttp中通过Request构造时添加:
header(name,value)
:设置HTTP头的唯一值,若请求已经存在则替换掉。addHeader(name,value)
:添加新值,若请求头中已经存在此name还会继续添加(此时,请求头中便会存在多个name相同而value不同的“键值对”)。header(name)
:读取唯一值或多个值的最后一个值headers(name)
:获取所有值构造Request时,必须设定Url,默认是GET模式:
Request request = new Request.Builder() .url("http://127.0.0.1") .addHeader("h1", "v1") .addHeader("h1", "v2") .header("uh", "v1") .header("uh", "v2") .build(); System.out.println(request.toString()); // Request{method=GET, url=http://127.0.0.1/, headers=[h1:v1, h1:v2, uh:v2]}
使用OkHttp,需要:
Request默认就是Get请求,所以构造时可以省略Get;Get请求参数通过queryParameter添加。
以请求百度为例,通过newCall会同步调用,其返回内容可通过body来获取;
private static void getSync() throws IOException { String url = "http://wwww.baidu.com"; Request request = new Request.Builder() .url(url) // 可以字符串,也可以是HttpUrl .build(); Call call = httpClient.newCall(request); Response resp = call.execute(); if (resp.code() == 200) { System.out.println("Response: " + resp.body().string()); } else { // Error handle System.out.println("Code:" + resp.code() + ", Msg:" + resp.message()); } }
通过enqueue,可提交异步请求;请求的应答通过回调Callback返回。
private static void getAsync() { HttpUrl url = buildUrl("http://127.0.0.1:7087/study/test/testEvent", new HashMap<String, String>(){{ put("msg", "Test Msg event"); }}); // http://127.0.0.1:7087/study/test/testEvent?msg=Test%20Msg%20event Request request = new Request.Builder() .url(url) .get() //默认就是GET请求,可以不写 .build(); Call call = httpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("Fail: " + e.getMessage()); } @Override public void onResponse(Call call, Response resp) throws IOException { System.out.println("onResponse: " + resp.body().string()); } }); }
Request中通过Post来标识Post请求,并设定Post的Body内容。
Json是常用的序列化方式,只需把要传递的对象序列化为Json字符串,然后以字符串Body的方式传递到服务端。
private static void postJsonBody() throws IOException { MediaType mediaType = MediaType.parse("application/json;charset=UTF-8"); RequestBody reqBody = RequestBody.create("msg for test", mediaType); Request request = new Request.Builder() .url("http://127.0.0.1:7087/study/test/postMsg") .post(reqBody) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string()); }
Form表单都是以键值对的方式传递内容到服务端的,通过FormBody可方便地构造。
private static void postFormBody() throws IOException { RequestBody reqBody = new FormBody.Builder() .add("msg", "form test") .add("others", "other params") .build(); Request request = new Request.Builder() .url("http://127.0.0.1:7087/study/test/formMsg") .post(reqBody) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string()); }
上传文件时,使用MultipartBody,并可通过MediaType来设定媒体类型:
private static void uploadOneFile() throws IOException { File file = new File("D:\\tmp\\Python-learning.md"); MediaType mediaType = MediaType.parse("application/octet-stream"); //设置类型为八位字节流 RequestBody reqBody = RequestBody.create(file, mediaType); MultipartBody multiBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("id", "0") // for test .addFormDataPart("file", file.getName(), reqBody) .build(); Request request = new Request.Builder() // .header("Authorization", "Bearer ****************") //添加请求头的身份认证Token .url("http://127.0.0.1:7087/study/test/uploadFile") .post(multiBody) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string()); }
要上传多个文件,就需要构造多份文件相关的RequestBody,然后依次添加到MultipartBody中:
private static void uploadMultiFile() throws IOException { List<String> lstFile = Lists.newArrayList("D:\\tmp\\Python-learning.md", "D:\\tmp\\WebRTC-Learning.md"); MediaType mediaType = MediaType.parse("application/octet-stream"); //设置类型为八位字节流 MultipartBody.Builder multiBody = new MultipartBody.Builder() .setType(MultipartBody.FORM); for(String f : lstFile){ File file = new File(f); RequestBody fileBody = RequestBody.create(file, mediaType); multiBody.addFormDataPart("file", file.getName(), fileBody); } Request request = new Request.Builder() .url("http://127.0.0.1:7087/study/test/uploadMultiFile") .post(multiBody.build()) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string()); }
Put与Post类似,只是Request时用Put标识。
以传递Json格式的Body为例:
MediaType mediaType = MediaType.parse("application/json;charset=UTF-8"); RequestBody reqBody = RequestBody.create("{\"page\":0,\"query\":\"info to query\",\"size\":0}", mediaType); Request request = new Request.Builder() .url("http://127.0.0.1:7087/study/test/testPut") .put(reqBody) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string());
通过参数来传递请求的内容;但是Put要求必须传递Body,此时可构造一个空Body:
HttpUrl url = buildUrl("http://127.0.0.1:7087/study/test/paramPut", new HashMap<String, String>() {{ put("msg", "Test Msg event"); put("others", "other params"); }}); RequestBody reqBody = RequestBody.create(new byte[]{}, null); Request request = new Request.Builder() .url(url) .put(reqBody) .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string());
Request中通过Delete来标识请求,默认是通过参数方式的,也可通过RequestBody来传递。
HttpUrl url = buildUrl("http://127.0.0.1:7087/study/test/testDelete", new HashMap<String, String>() {{ put("id", "delId"); }}); Request request = new Request.Builder() .url(url) .delete() .build(); Response resp = httpClient.newCall(request).execute(); System.out.println("Response: " + resp.body().string());