目录

常见的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(需要引入对应的依赖)。