HttpClient、OKhttp、RestTemplate对比
一、三者的对比HttpClient:代码复杂,还得操心资源回收等。代码很复杂,冗余代码多,不建议直接使用。RestTemplate: 是 Spring 提供的用于访问Rest服务的客户端, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。okhttp:OkHttp是一个高效的HTTP客户端,允许所有同一个主机地址的请求共享同一个socket连接;连
一、三者的对比
- HttpClient:代码复杂,还得操心资源回收等。
代码很复杂,冗余代码多,不建议直接使用。
- RestTemplate: 是
Spring 提供的用于访问Rest服务的客户端
, RestTemplate 提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 - okhttp:OkHttp是一个高效的HTTP客户端,
允许所有同一个主机地址的请求共享同一个socket连接
;连接池减少请求延时;透明的GZIP压缩减少响应数据的大小;缓存响应内容,避免一些完全重复的请求
二、HttpClient的使用
HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包
,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。
- 使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。
- 创建HttpClient对象。
- 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。
- 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
- 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
- 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。
- 释放连接。无论执行方法是否成功,都必须释放连接
- pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
- httpClient–Get
public class HttpClientExample_get {
public static void main(String[] args) throws IOException {
HttpGet request = new HttpGet("https://httpbin.org/get");
// add request headers
request.addHeader("custom-key", "test");
request.addHeader(HttpHeaders.USER_AGENT, "Googlebot");
try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(request)) {
// Get HttpResponse Status
System.out.println(response.getProtocolVersion()); // HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode()); // 200
System.out.println(response.getStatusLine().getReasonPhrase()); // OK
System.out.println(response.getStatusLine().toString()); // HTTP/1.1 200 OK
HttpEntity entity = response.getEntity();
if (entity != null) {
// return it as a String
String result = EntityUtils.toString(entity);
System.out.println(result);
}
}
}
}
- httpClient–Post
public class HttpClientExample_post {
public static void main(String[] args) {
try {
String result = sendPOST("https://httpbin.org/post");
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
private static String sendPOST(String url) throws IOException {
String result = "";
HttpPost post = new HttpPost(url);
// add request parameters or form parameters
List<NameValuePair> urlParameters = new ArrayList<>();
urlParameters.add(new BasicNameValuePair("username", "abc"));
urlParameters.add(new BasicNameValuePair("password", "123"));
urlParameters.add(new BasicNameValuePair("custom", "secret"));
post.setEntity(new UrlEncodedFormEntity(urlParameters));
try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(post)){
result = EntityUtils.toString(response.getEntity());
}
return result;
}
}
- httpClient–PostWithJson
public class HttpClientExample_JsonPost {
public static void main(String[] args) {
try {
String result = sendPOST("https://httpbin.org/post");
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
private static String sendPOST(String url) throws IOException {
String result = "";
HttpPost post = new HttpPost(url);
String json = "{" +
"\"name\":\"mkyong\"," +
"\"notes\":\"hello\"" +
"}";
// send a JSON data
post.setEntity(new StringEntity(json));
try (CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = httpClient.execute(post)) {
result = EntityUtils.toString(response.getEntity());
}
return result;
}
}
- HttpClient认证
public class HttpClientAuthentication {
public static void main(String[] args) throws IOException {
HttpGet request = new HttpGet("http://localhost:8080/books");
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials("user", "password")
);
try (CloseableHttpClient httpClient = HttpClientBuilder.create()
.setDefaultCredentialsProvider(provider)
.build();
CloseableHttpResponse response = httpClient.execute(request)) {
// 401 if wrong user/password
System.out.println(response.getStatusLine().getStatusCode());
HttpEntity entity = response.getEntity();
if (entity != null) {
// return it as a String
String result = EntityUtils.toString(entity);
System.out.println(result);
}
}
}
}
三、OKHttp的使用
-
出现背景
网络访问的高效性要求,可以说是为高效而生
-
解决思路
- 提供了对 HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接
- 如果 HTTP/2 和 SPDY 不可用,OkHttp会使用连接池来复用连接以提高效率
- 提供了对 GZIP 的默认支持来降低传输内容的大小
- 提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
- 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址
- OkHttp3设计思路
- Requests(请求): 每一个HTTP请求中都应该包含一个URL,一个GET或POST方法以及Header或其他参数,当然还可以含特定内容类型的数据流。
- Responses(响应): 响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
- 代码
- pom.xml
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
- 创建OkHttpClient实例
简单来说,通过OkHttpClient可以发送一个Http请求,并读取该Http请求的响应,它是一个生产Call的工厂
。此外,受益于一个共享的响应缓存/线程池/复用的连接等因素,绝大多数应用使用一个OkHttpClient实例,便可以满足整个应用的Http请求。
三种创建实例的方法:
- 创建一个默认配置OkHttpClient,可以使用默认的构造函数。
- 通过new OkHttpClient.Builder()方法来一步一步配置一个OkHttpClient实例。
- 如果要求使用现有的实例,可以通过newBuilder()方法来进行构造。
OkHttpClient client = new OkHttpClient();
OkHttpClient clientWith30sTimeout = client.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
OkHttpClient client = client.newBuilder().build();
看一下OkHttpClient的源码,会发现缓存/代理等等需求,一应俱全的按照类封装到了Builder中。
Dispatcher dispatcher; // 分发
Proxy proxy; // 代理
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>(); // 拦截器
final List<Interceptor> networkInterceptors = new ArrayList<>(); // 网络拦截器
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache; // 缓存
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator; // 代理证书
Authenticator authenticator; // 证书
ConnectionPool connectionPool;
Dns dns; // DNS
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
- okhttp–get
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
- Request
简单看一下Request类,可以发现它代表一个Http请求,需要注意的是Request一旦build()之后,便不可修改。主要通过new Request.Builder()来一步一步构造的
。看一下Builder的代码。
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
- 默认是Get方法,
此外还创建了头信息。Headers类中是通过List<String> namesAndValues = new ArrayList<>(20)
,来存放头信息的,一开始我也很纳闷,头信息都是一对一对的为什么要用List,看一下源码发现,在存取的时候都是将索引+2或者-2。并且头信息可以存在多个相同的Key信息。
- 发起请求
跟到newCall()方法中发现,又使用OkHttpClient实例和Request的实例,一起构造了一个RealCall的实例。RealCall类简单做了一个托管并通过Dispather类对请求进行分发和执行,实际开启线程发起请求的方法就在这个类中
。随后又调用execute()方法,拿到了一个响应。这个execute()方法,实际上执行的就是RealCall中的execute()方法,最后调用了Dispatcher的execute()方法。
- Response
Response代表一个Http的响应,这个类的实例不可修改。
一个简单的Get请求和说明就结束了
- okhttp–post
- POST提交字符串
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
MediaType用于描述Http请求和响应体的内容类型
,也就是Content-Type。一次请求就是向目标服务器发送一串文本。什么样的文本?有下面结构的文本。
- 例子
POST /meme.php/home/user/login HTTP/1.1
Host: 114.215.86.90
Cache-Control: no-cache
Postman-Token: bd243d6b-da03-902f-0a2c-8e9377f6f6ed
Content-Type: application/x-www-form-urlencoded
tel=13637829200&password=123456
例如,MediaType.parse(“application/json; charset=utf-8”);这个就带表请求体的类型为JSON格式的。定义好数据类型,还要将其变为请求体,最后通过post()方法,随请求一并发出。
- POST提交键值对
OkHttp也可以通过POST方式把键值对数据传送到服务器
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody formBody = new FormBody.Builder()
.add("platform", "android")
.add("name", "bug")
.add("subject", "XXXXXXXXXXXXXXX")
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " + response);
}
}
- HTTP头部的设置和读取
HTTP 头的数据结构是 Map<String, List<String>>类型。
也就是说,对于每个 HTTP 头,可能有多个值。但是大部分 HTTP 头都只有一个值,只有少部分 HTTP 头允许多个值。至于name的取值说明,可以查看这个请求头大全。
OkHttp的处理方式是:
- 使用header(name,value)来设置HTTP头的唯一值,如果请求中已经存在响应的信息那么直接替换掉。
- 使用addHeader(name,value)来补充新值,如果请求头中已经存在name的name-value,那么还会继续添加,请求头中便会存在多个name相同而value不同的“键值对”。
- 使用header(name)读取唯一值或多个值的最后一个值
- 使用headers(name)获取所有值
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://github.com")
.header("User-Agent", "My super agent")
.addHeader("Accept", "text/html")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("服务器端错误: " + response);
}
System.out.println(response.header("Server"));
System.out.println(response.headers("Set-Cookie"));
四、RestTemplate的使用
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。
RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 初始化(3种)
RestTemplate restTemplate = new RestTemplate();
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
当然如果想使用OkHttp的话也得引入相应的jar包
- 默认配置
默认情况下RestTemplate自动帮我们注册了一组HttpMessageConverter用来处理一些不同的contentType的请求
。如果现有的转换器不能满足你的需求,你还可以实现org.springframework.http.converter.HttpMessageConverter接口自己写一个。详情参考官方api。其他相关的配置,也可以在官方文档中查看。当然,对于一个请求来说,超期时间,请求连接时间等都是必不可少的参数,为了更好的适应业务需求,所以可以自己修改restTemplate的配置
。
- RestTemplate使用总结
RestTemplate提供了六种常用的HTTP方法实现远程服务调用,
RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型。
getForObject – 发送GET请求,
将HTTP response转换成一个指定的object对象
postForEntity –发送POST请求,
将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象
(包含响应数据和响应头)每个HTTP方法对应的RestTemplate方法都有3种。
其中2种的url参数为字符串,URI参数变量分别是Object数组和Map,第3种使用URI类型作为参数
exchange 和execute方法比上面列出的其它方法(如getForObject、postForEntity等)使用范围更广,
允许调用者指定HTTP请求的方法(GET、POST、PUT等),并且可以支持像HTTP PATCH(部分更新)
。
- GET 请求
做好了准备工作,先来看使用 RestTemplate 发送 GET 请求。在 RestTemplate 中,和 GET 请求相关的方法有如下几个:
- 代码
public class TemplateGet {
public static void main(String[] args) throws UnsupportedEncodingException {
//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
HttpClient httpClient = builder.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
//三种方式请求
String forObject = restTemplate.getForObject("https://httpbin.org/get", String.class);
ResponseEntity<String> forEntity = restTemplate.getForEntity("https://httpbin.org/get", String.class);
ResponseEntity<String> responseEntity = restTemplate.exchange("https://httpbin.org/get", HttpMethod.GET, null, String.class);
//遍历响应头
System.out.println("响应头--start");
forEntity.getHeaders().entrySet().forEach(System.out::println);
System.out.println("响应头--end");
assert forObject != null;
byte[] bytes = forObject.getBytes(StandardCharsets.UTF_8);
String res = new String(bytes, StandardCharsets.UTF_8);
System.out.println(res);
System.out.println(forEntity.getBody());
System.out.println(responseEntity.getBody());
}
}
- post请求
- json形式
public class TemplatePostJson {
public static void main(String[] args) {
//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
HttpClient httpClient = builder.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
String url = "https://httpbin.org/post";
String json = "{" +
"\"name\":\"mkyong\"," +
"\"notes\":\"hello\"" +
"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
headers.set("CustomHeader", "myCustom"); //Other headers
HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
//三种方式请求
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
String s = restTemplate.postForObject(url, httpEntity, String.class);
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
assert s != null;
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
String body=new String(bytes,StandardCharsets.UTF_8);
System.out.println(body);
System.out.println(responseEntity.getBody());
System.out.println("响应头--start");
stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);
System.out.println("响应头--end");
}
}
- map(表单)形式
public class TemplatePostMap {
public static void main(String[] args) {
//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
HttpClient httpClient = builder.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
String url = "https://httpbin.org/post";
MultiValueMap<Object, Object> map = new LinkedMultiValueMap<>();
map.add("aa", "aa");
map.add("bb", "bb");
map.add("cc", "cc");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
headers.set("CustomHeader", "myCustom"); //Other headers
HttpEntity<MultiValueMap<Object, Object>> httpEntity = new HttpEntity<>(map, headers);
//三种方式请求
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
String s = restTemplate.postForObject(url, httpEntity, String.class);
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
assert s != null;
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
String body=new String(bytes,StandardCharsets.UTF_8);
System.out.println(body);
System.out.println(responseEntity.getBody());
System.out.println("响应头--start");
stringResponseEntity.getHeaders().entrySet().forEach(System.out::println);
System.out.println("响应头--end");
}
}
其实,如果参数是一个 MultiValueMap 的实例,则以 key/value 的形式发送,如果是一个普通对象,则会被转成 json 发送。
- postForLocation
postForLocation 方法的返回值是一个 URL 对象
,因为 POST 请求一般用来添加数据,有的时候需要将刚刚添加成功的数据的 URL 返回来,此时就可以使用这个方法,一个常见的使用场景如用户注册功能,用户注册成功之后,可能就自动跳转到登录页面了,此时就可以使用该方法。
- PUT 请求
只要将 GET 请求和 POST 请求搞定了,接下来 PUT 请求就会容易很多了,PUT 请求本身方法也比较少,只有三个,如下:
这三个重载的方法其参数其实和 POST 是一样的,可以用 key/value 的形式传参,也可以用 JSON 的形式传参,无论哪种方式,都是没有返回值的
- DELETE 请求
和 PUT 请求一样,DELETE 请求也是比较简单的,只有三个方法,如下:
不同于 POST 和 PUT ,DELETE 请求的参数只能在地址栏传送,可以是直接放在路径中,也可以用 key/value 的形式传递
,当然,这里也是没有返回值的。
public class TemplateDeleteJson {
public static void main(String[] args) {
//生成一个设置了连接超时时间、请求超时时间、异常重试次数3次
RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(10000).setConnectTimeout(10000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setDefaultRequestConfig(config).setRetryHandler(new DefaultHttpRequestRetryHandler(3, false));
HttpClient httpClient = builder.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
String url = "https://httpbin.org/delete";
String json = "{" +
"\"name\":\"mkyong\"," +
"\"notes\":\"hello\"" +
"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);//Content-Type
headers.set("CustomHeader", "myCustom"); //Other headers
HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
restTemplate.delete(url,httpEntity);
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)