常见的http请求类库
OkHttp
- 依赖
- 工具类
- apache的httpcomponents-client
- 依赖
- 工具类
常见的http请求类库
- jdk自带的 java.net.HttpURLConnection:原生类库,使用起来比较麻烦;
- apache的 httpcomponents-client:在原生类库的基础上进行封装,支持https、连接池,适合普通、简单的http请求,异步请求、上传文件需要额外引入依赖;
- OkHttp:在原生类库的基础上进行封装,支持https、http2(向同一host发起的请求共用一个socket)、连接池、长连接、数据包GZIP压缩传输、请求缓存、文件上传、异步请求等特性,功能强大;
- spring-web的 RestTemplate:通常用在springboot、springcloud项目中,提供更高层级的抽象,屏蔽了底层具体实现,支持jdk原生http类库、httpcomponents-client、OkHttp等底层实现切换。
非单例模式下,OkHttp的性能更好,HttpClient创建连接比较耗时,因此多数情况下这些资源会写成单例模式;单例模式下,HttpClient速度略快一些,二者性能相差不大。
很多框架都集成了 httpcomponents-client、OkHttp,比如springboot。
OkHttp
github:https://github.com/square/okhttp
依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
工具类
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* OkHttp工具类
*/
@Slf4j
public class OkHttpClientUtil {
/**
* OkHttp客户端,直接 new OkHttpClient() 创建时使用默认配置
*/
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
.followRedirects(false)
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.build();
/**
* http状态码-成功
*/
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
private OkHttpClientUtil() {
}
/**
* get请求-携带自定义的请求参数、请求头参数
*
* @param url 接口地址
* @param paramMap 请求参数map,允许为空
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws IOException IO异常
*/
public static String doGet(String url, Map<String, String> paramMap, Map<String, String> headerMap) throws IOException {
if (StringUtils.isBlank(url)) {
throw new IllegalArgumentException("url不能为空");
}
HttpUrl.Builder httpUrlBuilder = HttpUrl.parse(url).newBuilder();
//参数也可以直接拼接在url中
if (MapUtils.isNotEmpty(paramMap)) {
paramMap.keySet().forEach(key -> httpUrlBuilder.addQueryParameter(key, paramMap.get(key)));
}
//兼容headerMap为null
Headers headers = headerMap == null ? Headers.of(Maps.newHashMap()) : Headers.of(headerMap);
Request request = new Request.Builder()
.url(httpUrlBuilder.build())
.headers(headers)
// .get() 默认就是get,可缺省
.build();
return doExecute(request);
}
/**
* get请求
*
* @param url 接口地址
* @return String 接口响应数据
* @throws IOException IO异常
*/
public static String doGet(String url) throws IOException {
//也可以直接 Request request = new Request.Builder().url(url).build(); 构建请求
return doGet(url, null, null);
}
/**
* post请求-携带json格式的请求参数
*
* @param url 接口地址
* @param jsonStr 请求参数序列化得到的json字符串。如果是实体对象、map,可以先自行转换为json字符串
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws IOException IO异常
*/
public static String doPostJson(String url, String jsonStr, Map<String, String> headerMap) throws IOException {
if (StringUtils.isAnyBlank(url, jsonStr)) {
throw new IllegalArgumentException("url或jsonStr为空");
}
RequestBody requestBody = RequestBody.Companion.create(jsonStr, MediaType.parse("application/json; charset=utf-8"));
Headers headers = headerMap == null ? Headers.of(Maps.newHashMap()) : Headers.of(headerMap);
Request request = new Request.Builder()
.url(url)
.headers(headers)
.post(requestBody)
.build();
return doExecute(request);
}
/**
* 发起http请求,解析响应
*
* @param request http请求
* @return String 接口响应数据
* @throws IOException IO异常
*/
private static String doExecute(final Request request) throws IOException {
log.info("发起http请求 request={}", request);
try (Response response = OK_HTTP_CLIENT.newCall(request).execute()) {
log.info("http响应数据 response={}", response);
//也可以使用 response.isSuccessful() 进行判断
int statusCode = response.code();
if (statusCode != HTTP_STATUS_CODE_SUCCESS) {
log.error("http响应状态码不是成功 statusCode={}", statusCode);
// throw new HttpResponseException(statusCode, null); 自定义异常
throw new RuntimeException("http响应状态码不是成功 statusCode=" + statusCode);
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
log.error("http响应 responseBody为null");
// throw new HttpResponseException(statusCode, null); 自定义异常
throw new RuntimeException("http响应 responseBody为null");
}
return responseBody.string();
}
}
}
- 采用 builder 模式,代码简洁;
- http请求配置(超时、代理、重定向等)是 HttpClient 级别的,不能针对单个http请求进行配置;
- 构建 Request 时,可指定请求方式,缺省默认为get;
- Call call = okHttpClient.newCall(request); OkHttp 将请求封装为 Call 执行,execute() 是同步执行,enqueue() 是放到队列中异步执行。
apache的httpcomponents-client
github:https://github.com/apache/httpcomponents-client
依赖
一般使用同步请求即可
<properties>
<httpclient.version>4.5.12</httpclient.version>
</properties>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
如果需要支持异步请求、文件上传,还需要额外引入依赖
<!-- 异步请求-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>${httpclient.version}</version>
</dependency>
工具类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.Args;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* HttpClient工具类
*/
@Slf4j
public class HttpClientUtil {
/**
* http客户端
*/
private static final CloseableHttpClient HTTP_CLIENT = HttpClients.createDefault();
/**
* 请求配置
*/
private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom()
.setRedirectsEnabled(false)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(5000)
.setConnectTimeout(10000)
.build();
/**
* http状态码-成功
*/
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
private HttpClientUtil() {
}
/**
* get请求-携带自定义的请求参数、请求头参数
*
* @param url 接口地址
* @param paramMap 请求参数map,允许为空
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws URISyntaxException URI语法异常
* @throws IOException IO异常
*/
public static String doGet(String url, Map<String, String> paramMap, Map<String, String> headerMap) throws URISyntaxException, IOException {
Args.notNull(url, "url");
//添加请求参数
URIBuilder uriBuilder = new URIBuilder(url);
if (MapUtils.isNotEmpty(paramMap)) {
paramMap.keySet().forEach(key -> uriBuilder.addParameter(key, paramMap.get(key)));
}
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.setConfig(REQUEST_CONFIG);
return execute(httpGet, headerMap);
}
/**
* get请求-不携带自定义参数
*
* @param url 接口地址
* @return String 接口响应数据
* @throws URISyntaxException URI语法异常
* @throws IOException IO异常
*/
public static String doGet(String url) throws URISyntaxException, IOException {
return doGet(url, null, null);
}
/**
* post请求-携带自定义参数
*
* @param url 接口地址
* @param paramMap 请求参数map,允许为空
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws URISyntaxException URI语法异常
* @throws IOException IO异常
*/
public static String doPost(String url, Map<String, String> paramMap, Map<String, String> headerMap) throws URISyntaxException, IOException {
Args.notNull(url, "url");
//添加请求参数
URIBuilder uriBuilder = new URIBuilder(url);
if (MapUtils.isNotEmpty(paramMap)) {
paramMap.keySet().forEach(key -> uriBuilder.addParameter(key, paramMap.get(key)));
}
HttpPost httpPost = new HttpPost(uriBuilder.build());
httpPost.setConfig(REQUEST_CONFIG);
return execute(httpPost, headerMap);
}
/**
* post请求-携带json格式的请求参数
*
* @param url 接口地址
* @param jsonStr 请求参数序列化得到的json字符串
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws IOException IO异常
*/
public static String doPostJson(String url, String jsonStr, Map<String, String> headerMap) throws IOException {
Args.notNull(url, "url");
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(REQUEST_CONFIG);
//设置请求体
if (StringUtils.isNotBlank(jsonStr)) {
StringEntity stringEntity = new StringEntity(jsonStr, ContentType.APPLICATION_JSON);
httpPost.setEntity(stringEntity);
}
return execute(httpPost, headerMap);
}
/**
* 添加请求头并发起http请求
*
* @param request http请求
* @param headerMap 请求头map,允许为空
* @return String 接口响应数据
* @throws IOException IO异常
*/
private static String execute(HttpUriRequest request, Map<String, String> headerMap) throws IOException {
if (MapUtils.isNotEmpty(headerMap)) {
headerMap.keySet().forEach(key -> request.addHeader(key, headerMap.get(key)));
}
return doExecute(request);
}
/**
* 发起http请求,解析响应
*
* @param request http请求
* @return String 接口响应数据
* @throws IOException IO异常
*/
private static String doExecute(final HttpUriRequest request) throws IOException {
log.info("发起http请求 request={}", request);
try (CloseableHttpResponse response = HTTP_CLIENT.execute(request)) {
log.info("http响应数据 response={}", response);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HTTP_STATUS_CODE_SUCCESS) {
log.error("响应状态码不是成功 statusCode={}", statusCode);
throw new HttpResponseException(statusCode, null);
}
//返回对方接口的响应数据,通常是json字符串
HttpEntity responseEntity = response.getEntity();
return EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
}
}
}
- get、post、put、delete请求方式的操作都差不多,可根据需要自行补充;
- http请求配置(超时、代理、重定向等)是 Request 级别的,可针对单个http请求进行配置;
- 上述使用的 CloseableHttpClient 是同步请求客户端,异步请求客户端为 CloseableHttpAsyncClient(需要引入对应的依赖)。