超詳細的日常開發必備神器 HttpUtil

語言: CN / TW / HK

​一、介紹

最近在工作中需要在後臺呼叫各種上傳、下載、以及第三方服務介面,經過研究決定使用 HttpClient,自己封裝了一個 HttpClient 工具類,簡單輕鬆的實現get、post、put、delete 以及上傳、下載請求,在此分享給大家。

二、實踐應用

本文基於 HttpClient4.5.5 版本進行開發,也是現在最新的版本,之所以要提供版本說明,是因為 HttpClient 3 版本和 HttpClient 4 版本 API 差別還是很多大的,你把 HttpClient 3 版本的程式碼拿到 HttpClient 4 上面執行不起來,會報錯的。所以在使用之前,一定要注意 HtppClient 的版本問題。

話不多說,直接上程式碼!

2.1、引用 HttpClient  依賴包

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.10</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.5.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.68</version>
</dependency>

2.2、編寫工具類(重點)

本次採用單利模式來初始化客戶端,並用執行緒池來管理,同時支援http和https​協議,專案啟動之後,無需手動關閉​httpClient客戶端!

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Objects;

public class HttpUtils {

    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    private HttpUtils() {}

    //多執行緒共享例項
    private static CloseableHttpClient httpClient;

    static {
        SSLContext sslContext = createSSLContext();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        // 註冊http套接字工廠和https套接字工廠
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", sslsf)
                .build();
        // 連線池管理器
        PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connMgr.setMaxTotal(300);//連線池最大連線數
        connMgr.setDefaultMaxPerRoute(300);//每個路由最大連線數,設定的過小,無法支援大併發
        connMgr.setValidateAfterInactivity(5 * 1000); //在從連線池獲取連線時,連線不活躍多長時間後需要進行一次驗證
        // 請求引數配置管理器
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(60000)
                .setSocketTimeout(60000)
                .setConnectionRequestTimeout(60000)
                .build();
        // 獲取httpClient客戶端
        httpClient = HttpClients.custom()
                .setConnectionManager(connMgr)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    /**
     * GET請求
     * @param url
     * @return
     */
    public static String getUrl(String url) {
        return sendHttp(HttpMethod.GET, url, null, null);
    }

    /**
     * GET請求/帶頭部的資訊
     * @param url
     * @param header
     * @return
     */
    public static String getUrl(String url, Map<String, String> header) {
        return sendHttp(HttpMethod.GET, url, header, null);
    }

    /**
     * POST請求/無引數
     * @param url
     * @return
     */
    public static String postJson(String url) {
        return postJson(url, null, null);
    }

    /**
     * POST請求/有引數
     * @param url
     * @param param
     * @return
     */
    public static String postJson(String url, String param) {
        return postJson(url, null, param);
    }

    /**
     * POST請求/無引數帶頭部
     * @param url
     * @param header
     * @return
     */
    public static String postJson(String url, Map<String, String> header) {
        return postJson(url, header, null);
    }

    /**
     * POST請求/有引數帶頭部
     * @param url
     * @param header
     * @param params
     * @return
     */
    public static String postJson(String url, Map<String, String> header, String params) {
        return sendHttp(HttpMethod.POST, url, header, params);
    }

    /**
     * 上傳檔案流
     * @param url
     * @param header
     * @param param
     * @param fileName
     * @param inputStream
     * @return
     */
    public static RequestResult postUploadFileStream(String url, Map<String, String> header, Map<String, String> param, String fileName, InputStream inputStream) {
        byte[] stream = inputStream2byte(inputStream);
        return postUploadFileStream(url, header, param, fileName, stream);
    }

    /**
     * 上傳檔案
     * @param url 上傳地址
     * @param header 請求頭部
     * @param param 請求表單
     * @param fileName 檔名稱
     * @param stream 檔案流
     * @return
     */
    public static RequestResult postUploadFileStream(String url, Map<String, String> header, Map<String, String> param, String fileName, byte[] stream) {
        String infoMessage =  new StringBuilder().append("request postUploadFileStream,url:").append(url).append(",header:").append(header.toString()).append(",param:").append(JSONObject.toJSONString(param)).append(",fileName:").append(fileName).toString();
        log.info(infoMessage);
        RequestResult result = new RequestResult();
        if(StringUtils.isBlank(fileName)){
            log.warn("上傳檔名稱為空");
            throw new RuntimeException("上傳檔名稱為空");
        }
        if(Objects.isNull(stream)){
            log.warn("上傳檔案流為空");
            throw new RuntimeException("上傳檔案流為空");
        }
        CloseableHttpResponse response = null;
        try {
            ContentType contentType = ContentType.MULTIPART_FORM_DATA.withCharset("UTF-8");
            HttpPost httpPost = new HttpPost(url);
            if (Objects.nonNull(header) && !header.isEmpty()) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                    if(log.isDebugEnabled()){
                        log.debug(entry.getKey() + ":" + entry.getValue());
                    }
                }
            }
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setCharset(Charset.forName("UTF-8"));//使用UTF-8
            builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);//設定瀏覽器相容模式
            if (Objects.nonNull(param) && !param.isEmpty()) {
                for (Map.Entry<String, String> entry : param.entrySet()) {
                    if (log.isDebugEnabled()) {
                        log.debug(entry.getKey() + ":" + entry.getValue());
                    }
                    builder.addPart(entry.getKey(), new StringBody(entry.getValue(), contentType));
                }
            }
            builder.addBinaryBody("file",  stream, contentType, fileName);//封裝上傳檔案
            httpPost.setEntity(builder.build());
            //請求執行,獲取返回物件
            response = httpClient.execute(httpPost);
            log.info("postUploadFileStream response status:{}", response.getStatusLine());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                result.setSuccess(true);
            }
            HttpEntity httpEntity = response.getEntity();
            if (Objects.nonNull(httpEntity)) {
                String content = EntityUtils.toString(httpEntity, "UTF-8");
                log.info("postUploadFileStream response body:{}", content);
                result.setMsg(content);
            }

        } catch (Exception e) {
            log.error(infoMessage + " failure", e);
            result.setMsg("請求異常");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return result;
    }

    /**
     * 從下載地址獲取檔案流(如果連結出現雙斜槓,請用OKHttp)
     * @param url
     * @return
     */
    public static ByteArrayOutputStream getDownloadFileStream(String url) {
        String infoMessage = new StringBuilder().append("request getDownloadFileStream,url:").append(url).toString();
        log.info(infoMessage);
        ByteArrayOutputStream byteOutStream = null;
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(new HttpGet(url));
            log.info("getDownloadFileStream response status:{}", response.getStatusLine());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                //請求成功
                HttpEntity entity = response.getEntity();
                if (entity != null && entity.getContent() != null) {
                    //複製輸入流
                    byteOutStream = cloneInputStream(entity.getContent());
                }
            }
        } catch (Exception e) {
            log.error(infoMessage + " failure", e);
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return byteOutStream;
    }
    
    /**
     * 表單請求
     * @param url 地址
     * @param header 請求頭部
     * @param param 請求表單
     * @return
     */
    public static RequestResult sendPostForm(String url, Map<String, String> header, Map<String, String> param) {
        String infoMessage =  new StringBuilder().append("request postForm,url:").append(url).append(",header:").append(JacksonUtils.toJson(header)).append(",param:").append(JacksonUtils.toJson(param)).toString();
        if(log.isDebugEnabled()){
            log.debug(infoMessage);
        }
        RequestResult result = new RequestResult();
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-type", "application/x-www-form-urlencoded");
            if (Objects.nonNull(header) && !header.isEmpty()) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                    if(log.isDebugEnabled()){
                        log.debug(entry.getKey() + ":" + entry.getValue());
                    }
                }
            }
            List<NameValuePair> nameValuePairs = new ArrayList<>();
            if (Objects.nonNull(param) && !param.isEmpty()) {
                for (Map.Entry<String, String> entry : param.entrySet()) {
                    if (log.isDebugEnabled()) {
                        log.debug(entry.getKey() + ":" + entry.getValue());
                    }
                    nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
            }
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, Charset.forName("UTF-8")));
            //請求執行,獲取返回物件
            response = httpClient.execute(httpPost);
            if(log.isDebugEnabled()){
                log.debug("postForm response status:{}", response.getStatusLine());
            }
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                result.setSuccess(true);
            }
            HttpEntity httpEntity = response.getEntity();
            if (Objects.nonNull(httpEntity)) {
                String content = EntityUtils.toString(httpEntity, "UTF-8");
                if(log.isDebugEnabled()){
                    log.debug("postForm response body:{}", content);
                }
                result.setMsg(content);
            }
        } catch (Exception e) {
            log.error(infoMessage + " failure", e);
            result.setMsg("請求異常");
        } finally {
            HttpClientUtils.closeQuietly(response);
        }
        return result;
    }


    /**
     * 傳送http請求(通用方法)
     * @param httpMethod 請求方式(GET、POST、PUT、DELETE)
     * @param url        請求路徑
     * @param header     請求頭
     * @param params     請求body(json資料)
     * @return 響應文字
     */
    public static String sendHttp(HttpMethod httpMethod, String url, Map<String, String> header, String params) {
        String infoMessage = new StringBuilder().append("request sendHttp,url:").append(url).append(",method:").append(httpMethod.name()).append(",header:").append(JSONObject.toJSONString(header)).append(",param:").append(params).toString();
        log.info(infoMessage);
        //返回結果
        String result = null;
        CloseableHttpResponse response = null;
        long beginTime = System.currentTimeMillis();
        try {
            ContentType contentType = ContentType.APPLICATION_JSON.withCharset("UTF-8");
            HttpRequestBase request = buildHttpMethod(httpMethod, url);
            if (Objects.nonNull(header) && !header.isEmpty()) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    //列印頭部資訊
                    if(log.isDebugEnabled()){
                        log.debug(entry.getKey() + ":" + entry.getValue());
                    }
                    request.setHeader(entry.getKey(), entry.getValue());
                }
            }
            if (StringUtils.isNotEmpty(params)) {
                if(HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod)){
                    ((HttpEntityEnclosingRequest) request).setEntity(new StringEntity(params, contentType));
                }
            }
            response = httpClient.execute(request);
            HttpEntity httpEntity = response.getEntity();
            log.info("sendHttp response status:{}", response.getStatusLine());
            if (Objects.nonNull(httpEntity)) {
                result = EntityUtils.toString(httpEntity, "UTF-8");
                log.info("sendHttp response body:{}", result);
            }
        } catch (Exception e) {
            log.error(infoMessage + " failure", e);
        } finally {
            HttpClientUtils.closeQuietly(response);//關閉返回物件
        }
        long endTime = System.currentTimeMillis();
        log.info("request sendHttp response time cost:" + (endTime - beginTime) + " ms");
        return result;
    }

    /**
     * 請求方法(全大些)
     */
    public enum HttpMethod {
        GET, POST, PUT, DELETE
    }

    /**
     * 上傳返回結果封裝
     */
    public static class RequestResult{
        private boolean isSuccess;//是否成功
        private String msg;//訊息

        public boolean isSuccess() {
            return isSuccess;
        }

        public RequestResult setSuccess(boolean success) {
            isSuccess = success;
            return this;
        }

        public String getMsg() {
            return msg;
        }

        public RequestResult setMsg(String msg) {
            this.msg = msg;
            return this;
        }

        public RequestResult() {
            this.isSuccess = false;
        }
    }

    /**
     * 構建請求方法
     * @param method
     * @param url
     * @return
     */
    private static HttpRequestBase buildHttpMethod(HttpMethod method, String url) {
        if (HttpMethod.GET.equals(method)) {
            return new HttpGet(url);
        } else if (HttpMethod.POST.equals(method)) {
            return new HttpPost(url);
        } else if (HttpMethod.PUT.equals(method)) {
            return new HttpPut(url);
        } else if (HttpMethod.DELETE.equals(method)) {
            return new HttpDelete(url);
        } else {
            return null;
        }
    }

    /**
     * 配置證書
     * @return
     */
    private static SSLContext createSSLContext(){
        try {
            //信任所有,支援匯入ssl證書
            TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
            return sslContext;
        } catch (Exception e) {
            log.error("初始化ssl配置失敗", e);
            throw new RuntimeException("初始化ssl配置失敗");
        }
    }

    /**
     * 複製檔案流
     * @param input
     * @return
     */
    private static ByteArrayOutputStream cloneInputStream(InputStream input) {
        try {
            ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) > -1) {
                byteOutStream.write(buffer, 0, len);
            }
            byteOutStream.flush();
            return byteOutStream;
        } catch (IOException e) {
            log.warn("copy InputStream error,{}", ExceptionUtils.getStackTrace(e));
        }
        return null;
    }

    /**
     * 輸入流轉位元組流
     * @param in
     * @return
     */
    private static byte[] inputStream2byte(InputStream in) {
        ByteArrayOutputStream bos = null;
        try {
            bos = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int n;
            while ((n = in.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            in.close();
            bos.close();
            byte[] buffer = bos.toByteArray();
            return buffer;
        } catch (IOException e) {
            log.warn("inputStream transfer byte error,{}", ExceptionUtils.getStackTrace(e));
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                log.error("clone inputStream error", e);
            }
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                log.error("clone outputStream error", e);
            }
        }
        return null;
    }

    public static void main(String[] args) {
        String url = "https://101.231.204.80:5000/gateway/api/queryTrans.do";
        String result = postJson(url);
        System.out.println(result);
    }
}

除了上傳、下載請求之外,預設封裝的請求引數格式都是application/json,如果不夠,可以根據自己的業務場景進行封裝處理!

其中sendHttp​是一個支援GET、POST、PUT、DELETE​請求的通用方法,上面介紹的getUrl、postJosn等方法,最終都會呼叫到這個方法!

2.3、介面請求示例

工具包封裝完成之後,在程式碼中使用起來也非常簡單,直接採用工具類方法就可以直接使用,例如下面以post方式請求某個介面!

public static void main(String[] args) throws Exception {
    String url = "https://101.231.204.80:5000/gateway/api/queryTrans.do";
    String result= HttpUtils.postJson(url);
    System.out.println(result);
}

三、小結

在編寫工具類的時候,需要注意的地方是,儘可能保證httpClient客戶端全域性唯一,也就是採用單利模式,如果我們每次請求都初始化一個客戶端,結束之後又將其關閉,在高併發的介面請求場景下,效能效率急劇下降!

HttpClients​客戶端的初始化引數配置非常豐富,本文預設初始化的執行緒池為300,在實際的業務開發中,大家還可以結合自己的業務場景進行調優,具體的配置可以參考官網文件,地址:Apache HttpComponents。