HttpClientUtil實(shí)踐

1、httpclient

核心模塊有
ManagedHttpClientConnection
PoolingHttpClientConnectionManager

鏈接池是由StrictConnPool維護(hù), PoolingHttpClientConnectionManager不直接維護(hù)鏈接池

2、httpclient實(shí)現(xiàn)類

名稱 說(shuō)明 備注
InternalHttpClient 默認(rèn)客戶端
MinimalHttpClient 最新客戶端,沒有認(rèn)證,代理的功能。(request execution via a proxy, state management, authentication and request redirects.)
CloseableHttpClient 客戶端的基礎(chǔ)封裝,提供公共流程

3、HttpClientConnectionManager實(shí)現(xiàn)類

  • BasicHttpClientConnectionManager
  • PoolingHttpClientConnectionManager
3.1、BasicHttpClientConnectionManager

單個(gè)連接的連接管理器,只支持一個(gè)鏈接。多線程使用時(shí)也只能一個(gè)線程執(zhí)行,其他線程會(huì)等待

3.1、PoolingHttpClientConnectionManager

維護(hù)連接請(qǐng)求池,并能夠?yàn)槎鄠€(gè)執(zhí)行線程連接請(qǐng)求提供服務(wù)。根據(jù)路由進(jìn)行區(qū)分,每個(gè)路由(域名+端口)默認(rèn)最大鏈接數(shù)為2,總共鏈接數(shù)默認(rèn)為20;

  • 默認(rèn)最大鏈接數(shù)20
  • 默認(rèn)最大路由鏈接數(shù)2
  • 默認(rèn)空閑鏈接存活時(shí)間 10s
  • 默認(rèn)空閑鏈接存活時(shí)間單位 s
  • 默認(rèn)鏈接存活時(shí)間 -1 (永久存活)
  • 默認(rèn)鏈接存活時(shí)間單位 s

備注:http中的鏈接不主動(dòng)關(guān)閉,鏈接池中的鏈接由IdleConnectionEvictor進(jìn)行維護(hù)管理;在未過(guò)期會(huì)一直保持鏈接狀態(tài);

   CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
// 在build時(shí)設(shè)置很多初始化配置,build方法是入口

1. httpclient

1.1、httpclient實(shí)現(xiàn)類
名稱 說(shuō)明 備注
InternalHttpClient 默認(rèn)客戶端 包含全部功能。 非公共類,在項(xiàng)目中無(wú)法直接通過(guò)new 創(chuàng)建客戶端
MinimalHttpClient 最新客戶端,沒有認(rèn)證,代理的功能。(request execution via a proxy, state management, authentication and request redirects.) 非公共類,在項(xiàng)目中無(wú)法直接通過(guò)new 創(chuàng)建客戶端。
CloseableHttpClient 客戶端的基礎(chǔ)封裝,提供公共流程 抽象類,直接new 需要重寫相關(guān)方法
1.2、httpclient創(chuàng)建方式說(shuō)明

通過(guò)上述說(shuō)明創(chuàng)建client只有兩種方式

  • 通過(guò)HttpClientBuilder build();
  • 直接new 自定義實(shí)現(xiàn)CloseableHttpClient 抽象類方法;

httpClients提供快速創(chuàng)建的方式

  • createDefault() 創(chuàng)建默認(rèn)的連接器,默認(rèn)為InternalHttpClient客戶端,包含多個(gè)功能。常用方式
  • createSystem() 通過(guò)系統(tǒng)參數(shù)創(chuàng)建連接器
  • createMinimal() 創(chuàng)建默認(rèn)最小客戶端
  • createMinimal(HttpClientConnectionManager) 創(chuàng)建默認(rèn)帶有鏈接池的客戶端

創(chuàng)建demo 1

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 整個(gè)連接池最大連接數(shù)
cm.setMaxTotal(100);
// 每路由最大連接數(shù),默認(rèn)值是2
cm.setDefaultMaxPerRoute(5);

CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).disableAutomaticRetries().build();

創(chuàng)建demo 2

CloseableHttpClient httpClient1 = HttpClients.createDefault();
1.3、創(chuàng)建流程說(shuō)明

創(chuàng)建client 必備的要素

  • ClientExecChain execChain
  • HttpClientConnectionManager connManager
  • HttpRoutePlanner routePlanner
  • Lookup<CookieSpecProvider> cookieSpecRegistry
  • Lookup<AuthSchemeProvider> authSchemeRegistry
  • CookieStore cookieStore
  • CredentialsProvider credentialsProvider
  • RequestConfig defaultConfig
  • List<Closeable> closeables

最佳實(shí)踐

1. 單線程測(cè)試場(chǎng)景
public class HttpClientPerformanceTest {
    // 測(cè)試參數(shù)配置
    private static final String TARGET_URL = "https://www.baidu.com";
    private static final int TOTAL_REQUESTS = 1; // 總請(qǐng)求次數(shù)
    private static final int WARMUP_ROUNDS = 10;   // 預(yù)熱輪次

    public static void main(String[] args) throws Exception {
        // 預(yù)熱(避免首次請(qǐng)求的初始化影響)
        // warmup();

        // 測(cè)試共用Client模式
        System.out.println("=== 測(cè)試共用HttpClient實(shí)例 ===");
        testSharedClient();

        // 測(cè)試每次新建Client模式
        System.out.println("\n=== 測(cè)試每次新建HttpClient ===");
        testNewClientEachTime();
    }

    private static void warmup() throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();
        for (int i = 0; i < WARMUP_ROUNDS; i++) {
            executeRequest(client);
        }
    }

    private static void testSharedClient() throws Exception {
        long totalTime = 0;
        long startTime = System.currentTimeMillis();

        CloseableHttpClient client = HttpClients.createDefault();
        for (int i = 0; i < TOTAL_REQUESTS; i++) {
            long requestStart = System.currentTimeMillis();
            executeRequest(client);
            long duration = (System.currentTimeMillis() - requestStart); // 毫秒
            totalTime += duration;

            if ((i + 1) % 20 == 0) {
                System.out.printf("已完成 %d 次請(qǐng)求,平均耗時(shí) %.2f ms%n",
                        i + 1, (double) totalTime / (i + 1));
            }
        }

        long overallDuration = System.currentTimeMillis() - startTime;
        printResult("共用實(shí)例", TOTAL_REQUESTS, overallDuration, totalTime);
    }

    private static void testNewClientEachTime() throws Exception {
        long totalTime = 0;
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < TOTAL_REQUESTS; i++) {
            long requestStart = System.currentTimeMillis();
            try (CloseableHttpClient client = HttpClients.createDefault()) {
                executeRequest(client);
            }
            long duration = (System.currentTimeMillis() - requestStart); // 毫秒
            totalTime += duration;

            if ((i + 1) % 10 == 0) {
                System.out.printf("已完成 %d 次請(qǐng)求,平均耗時(shí) %.2f ms%n",
                        i + 1, (double) totalTime / (i + 1));
            }
        }

        long overallDuration = System.currentTimeMillis() - startTime;
        printResult("新建實(shí)例", TOTAL_REQUESTS, overallDuration, totalTime);
    }

    private static void executeRequest(CloseableHttpClient client) throws Exception {
        HttpGet request = new HttpGet(TARGET_URL);
        CloseableHttpResponse response = client.execute(request);
        // 確保消費(fèi)實(shí)體內(nèi)容,才能釋放鏈接資產(chǎn),鏈接不會(huì)斷開
        // EntityUtils.consume(response.getEntity());
        String result = parseHttpEntity(response.getEntity());
        // 關(guān)閉連接,即斷開tcp鏈接,釋放linux層面的鏈接資源,這樣的話,每次請(qǐng)求都需要重新建立鏈接,效率很低
        // response.close();
    }

    private static void printResult(String mode, int count, long totalDuration, long sumOfIndividual) {
        System.out.println("\n測(cè)試結(jié)果 [" + mode + "]:");
        System.out.println("總請(qǐng)求次數(shù): " + count);
        System.out.println("總耗時(shí): " + totalDuration + " ms");
        System.out.printf("平均每次請(qǐng)求耗時(shí): %.2f ms%n", (double) totalDuration / count);
        System.out.printf("平均網(wǎng)絡(luò)耗時(shí): %.2f ms%n", (double) sumOfIndividual / count);
        System.out.printf("吞吐量: %.2f 請(qǐng)求/秒%n",
                count / (totalDuration / 1000.0));
    }

    public static String parseHttpEntity(HttpEntity httpEntity, String charset) throws IOException {
        charset = charset == null ? "utf-8" : charset;
        String entityStr = null;
        if (httpEntity == null) {
            return entityStr;
        } else {
            entityStr = EntityUtils.toString(httpEntity, charset);
            return entityStr;
        }
    }

    public static String parseHttpEntity(HttpEntity httpEntity) throws IOException {
        return parseHttpEntity(httpEntity, "utf-8");
    }
}

類別 請(qǐng)求1次 請(qǐng)求10次 請(qǐng)求100次 請(qǐng)求1000次
共享實(shí)例網(wǎng)絡(luò)平均耗時(shí) 325 60 24 16
每次創(chuàng)建實(shí)例網(wǎng)絡(luò)平均耗時(shí) 80 86 90 78.89

通過(guò)多次對(duì)比看,共享實(shí)例在單線程時(shí)耗時(shí)在請(qǐng)求量大時(shí),共享實(shí)例的效率更高;

2、多線程測(cè)試
package com.tianzehaoSpring;

import com.alibaba.fastjson.JSON;
import org.apache.http.client.ClientProtocolException;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;

/**
 * Hello world!
 *
 */
public class HttpClientUtilThreadTest
{
    public static void main( String[] args ) throws InterruptedException {
        for(int i = 0; i < 1; i++){
            httpClientTest();
        }
    }


    private static void httpClientTest() throws InterruptedException {
        int maxThreadTotal = 80;
        // List<String> urls = Arrays.asList("https://www.baidu.com","https://cn.bing.com/");
        List<String> urls = Arrays.asList("http://10.1.106.177");
        // CloseableHttpClient httpClient = getHttpClient(maxThreadTotal,urls.size());

        Map<String,List<Long>> wasteTimeMapWithHttpClient = new HashMap<>();
        Map<String,List<Long>> wasteTimeMapNoHttpClient = new HashMap<>();
        long startTime = System.currentTimeMillis();

        CountDownLatch withShareClientCountDown = new CountDownLatch(maxThreadTotal);
        CountDownLatch withNoShareClientCountDown = new CountDownLatch(maxThreadTotal);
        for (String url : urls) {

            List<Long> times = getBlankList(maxThreadTotal);

            wasteTimeMapWithHttpClient.put(url, times);

            CloseableHttpClient httpClient = getHttpClient(maxThreadTotal,urls.size());
            for(int i = 0; i < maxThreadTotal; i++){
                int index = i;
                Thread thread = new Thread(()->{
                    long start = System.currentTimeMillis();
                    HttpGet httpGet = new HttpGet(url);
                    try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                        EntityUtils.consume(response.getEntity());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    long wasteTime = System.currentTimeMillis() - start;
                    times.set(index, wasteTime);
                    withShareClientCountDown.countDown();
                });
                thread.setName("withShareClientCountDown" + i);
                thread.start();
            }
        }
        withShareClientCountDown.await();
        System.out.println("With HttpClient: " + (System.currentTimeMillis() - startTime) + "ms. " + "avg:" +  ((System.currentTimeMillis() - startTime)/maxThreadTotal) + "ms." );
        for (String url : urls) {
            List<Long> times = wasteTimeMapWithHttpClient.get(url);
            long totalTime = times.stream().filter(wasteTime-> wasteTime!=0).mapToLong(Long::longValue).sum();
            long avgTime = totalTime/times.size();
            System.out.println(JSON.toJSONString(times));
            System.out.println("With HttpClient: " + url + " avgTime: " + avgTime);
        }



        for (String url : urls) {
            List<Long> times = getBlankList(maxThreadTotal);
            wasteTimeMapNoHttpClient.put(url, times);

            for(int i = 0; i < maxThreadTotal; i++){
                int index = i;
                new Thread(()->{
                    long start = System.currentTimeMillis();
                    HttpGet httpGet = new HttpGet(url);
                    CloseableHttpClient httpClient = HttpClients.createDefault();
                    try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                        EntityUtils.consume(response.getEntity());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    long wasteTime = System.currentTimeMillis() - start;
                    times.set(index, wasteTime);
                    withNoShareClientCountDown.countDown();
                }).start();
            }
        }
        withNoShareClientCountDown.await();
        System.out.println("no HttpClient: " + (System.currentTimeMillis() - startTime) + "ms. " + "avg:" +  ((System.currentTimeMillis() - startTime)/maxThreadTotal) + "ms." );
        for (String url : urls) {
            List<Long> times = wasteTimeMapNoHttpClient.get(url);
            long totalTime = times.stream().filter(wasteTime-> wasteTime!=0).mapToLong(Long::longValue).sum();
            long avgTime = totalTime/times.size();
            System.out.println(JSON.toJSONString(times));
            System.out.println("no HttpClient: " + url + " avgTime: " + avgTime);
        }
        System.out.println("   ");
    }
    private static List<Long> getBlankList(int size){
        List<Long> list = new ArrayList<>();
        for(int i = 0; i < size; i++){
            list.add(0L);
        }
        return list;
    }


    public static CloseableHttpClient getHttpClient(Integer maxThread,Integer maxRoute){

        int MAX_TOTAL_CONNECTIONS = maxThread * maxRoute * 2;
        int DEFAULT_MAX_PER_ROUTE = maxThread * 2;
        int CONNECT_TIMEOUT = 5000;  // 建立與目標(biāo)主機(jī)連接的最大時(shí)間 默認(rèn) -1不限制
        int SOCKET_TIMEOUT = 10000;  // 等待數(shù)據(jù)傳輸?shù)淖畲髸r(shí)間 默認(rèn) -1不限制
        int CONNECTION_REQUEST_TIMEOUT = 2000; // 從連接池中獲取連接的超時(shí)時(shí)間 默認(rèn) -1不限制
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);

        // 配置全局請(qǐng)求參數(shù)
        RequestConfig requestConfig = RequestConfig.custom()
                // .setConnectTimeout(CONNECT_TIMEOUT)
                // .setSocketTimeout(SOCKET_TIMEOUT)
                // .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                .build();

        // 創(chuàng)建 HttpClient 實(shí)例
        return HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }
}

開啟多線程請(qǐng)求

類別 請(qǐng)求1次 請(qǐng)求10次 請(qǐng)求100次 請(qǐng)求500次
共享實(shí)例請(qǐng)求耗時(shí) 393 197 207 611
每次創(chuàng)建實(shí)例請(qǐng)求耗時(shí) 297 123 408 1257

結(jié)論: 不管是多線程,還是單線程,共享實(shí)例的效率更高,因?yàn)楣蚕韺?shí)例可以復(fù)用tcp鏈接,而每次創(chuàng)建實(shí)例則會(huì)重新建立tcp鏈接,效率較低。

常見問(wèn)題

1、內(nèi)存泄漏檢查

tcp 鏈接管理器會(huì)自動(dòng)關(guān)閉鏈接,httpclient對(duì)象在失去tcp鏈接引用后可被自動(dòng)回收;

2、共享實(shí)例阻塞人問(wèn)題

response 不消費(fèi),會(huì)導(dǎo)致鏈接無(wú)法釋放;無(wú)法再發(fā)起請(qǐng)求;

3、每次創(chuàng)建實(shí)例請(qǐng)求耗時(shí)高

共享實(shí)例測(cè)試時(shí) 關(guān)閉了鏈接,導(dǎo)致共享實(shí)例與每次創(chuàng)建實(shí)例測(cè)試結(jié)果相同,因?yàn)楣蚕韺?shí)例測(cè)試時(shí),關(guān)閉了鏈接

// 執(zhí)行該語(yǔ)句,會(huì)關(guān)閉鏈接,即斷開tcp鏈接,釋放linux層面的鏈接資源,這樣的話,每次請(qǐng)求都需要重新建立鏈接,效率很低。
response.close();

附錄: httpClientUtil

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpClientUtil {

    private static final String DEFAULT_CHARSET = "UTF-8";
    private static final int CONNECT_TIMEOUT = 5000; // 連接超時(shí)時(shí)間(毫秒)
    private static final int SOCKET_TIMEOUT = 5000;  // 讀取數(shù)據(jù)超時(shí)時(shí)間(毫秒)

    /**
     * 發(fā)送GET請(qǐng)求
     * @param url 請(qǐng)求地址
     * @param params 請(qǐng)求參數(shù)
     * @return 響應(yīng)內(nèi)容
     */
    public static String doGet(String url, Map<String, String> params) {
        return doGet(null, url, params, null, DEFAULT_CHARSET);
    }

    /**
     * 發(fā)送GET請(qǐng)求
     * @param httpClient 可選的HttpClient實(shí)例
     * @param url 請(qǐng)求地址
     * @param params 請(qǐng)求參數(shù)
     * @param headers 自定義請(qǐng)求頭
     * @param charset 字符編碼
     * @return 響應(yīng)內(nèi)容
     */
    public static String doGet(CloseableHttpClient httpClient, String url,
                               Map<String, String> params, Map<String, String> headers,
                               String charset) {
        // 構(gòu)建帶參數(shù)的URL
        if (params != null && !params.isEmpty()) {
            url = buildUrlWithParams(url, params);
        }

        HttpGet httpGet = new HttpGet(url);


        // 設(shè)置自定義Header
        if (headers != null && !headers.isEmpty()) {
            setHeaders(httpGet, headers);
        }

        boolean isShareHttpClient = httpClient != null;
        // 使用傳入的HttpClient或創(chuàng)建新的
        if (httpClient == null) {
            httpClient = HttpClients.createDefault();
        }

        try {
            CloseableHttpResponse response = httpClient.execute(httpGet)
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                return EntityUtils.toString(entity, charset);
            }
            return "";
        } catch (IOException e) {
            throw new RuntimeException("HTTP GET請(qǐng)求失敗: " + e.getMessage(), e);
        }finally {
            if(!isShareHttpClient){
                //非共享HttpClient,則關(guān)閉
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // 忽略關(guān)閉異常
                }
            }
        }
    }

    /**
     * 發(fā)送POST請(qǐng)求(表單形式)
     * @param url 請(qǐng)求地址
     * @param params 請(qǐng)求參數(shù)
     * @return 響應(yīng)內(nèi)容
     */
    public static String doPost(String url, Map<String, String> params) {
        return doPost(null, url, params, null, DEFAULT_CHARSET);
    }

    /**
     * 發(fā)送POST請(qǐng)求(表單形式)
     * @param httpClient 可選的HttpClient實(shí)例
     * @param url 請(qǐng)求地址
     * @param params 請(qǐng)求參數(shù)
     * @param headers 自定義請(qǐng)求頭
     * @param charset 字符編碼
     * @return 響應(yīng)內(nèi)容
     */
    public static String doPost(CloseableHttpClient httpClient, String url,
                                Map<String, String> params, Map<String, String> headers,
                                String charset) {
        HttpPost httpPost = new HttpPost(url);

        // 設(shè)置自定義Header
        if (headers != null && !headers.isEmpty()) {
            setHeaders(httpPost, headers);
        }

        // 設(shè)置表單參數(shù)
        if (params != null && !params.isEmpty()) {
            try {
                List<NameValuePair> paramList = new ArrayList<>();
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    paramList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                httpPost.setEntity(new UrlEncodedFormEntity(paramList, charset));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("不支持的編碼格式: " + charset, e);
            }
        }

        boolean isShareHttpClient = httpClient != null;
        // 使用傳入的HttpClient或創(chuàng)建新的
        if (httpClient == null) {
            httpClient = HttpClients.createDefault();
        }

        try {
            CloseableHttpResponse response = httpClient.execute(httpPost)
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                return EntityUtils.toString(entity, charset);
            }
            return "";
        } catch (IOException e) {
            throw new RuntimeException("HTTP POST請(qǐng)求失敗: " + e.getMessage(), e);
        }
        finally {
            if(!isShareHttpClient){
                //非共享HttpClient,則關(guān)閉
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // 忽略關(guān)閉異常
                }
            }
        }
    }

    /**
     * 發(fā)送POST請(qǐng)求(JSON形式)
     * @param url 請(qǐng)求地址
     * @param json JSON字符串
     * @return 響應(yīng)內(nèi)容
     */
    public static String doPostJson(String url, String json) {
        return doPostJson(null, url, json, null, DEFAULT_CHARSET);
    }

    /**
     * 發(fā)送POST請(qǐng)求(JSON形式)
     * @param httpClient 可選的HttpClient實(shí)例
     * @param url 請(qǐng)求地址
     * @param json JSON字符串
     * @param headers 自定義請(qǐng)求頭
     * @param charset 字符編碼
     * @return 響應(yīng)內(nèi)容
     */
    public static String doPostJson(CloseableHttpClient httpClient, String url,
                                    String json, Map<String, String> headers,
                                    String charset) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Content-Type", "application/json");

        // 設(shè)置自定義Header
        if (headers != null && !headers.isEmpty()) {
            setHeaders(httpPost, headers);
        }

        // 設(shè)置JSON請(qǐng)求體
        if (json != null) {
            httpPost.setEntity(new StringEntity(json, charset));
        }

        boolean isShareHttpClient = httpClient != null;
        // 使用傳入的HttpClient或創(chuàng)建新的
        if (httpClient == null) {
            httpClient = HttpClients.createDefault();
        }

        try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                return EntityUtils.toString(entity, charset);
            }
            return "";
        } catch (IOException e) {
            throw new RuntimeException("HTTP POST JSON請(qǐng)求失敗: " + e.getMessage(), e);
        }
        finally {
            if(!isShareHttpClient){
                //非共享HttpClient,則關(guān)閉
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // 忽略關(guān)閉異常
                }
            }
        }
    }

    /**
     * 構(gòu)建帶參數(shù)的URL
     */
    private static String buildUrlWithParams(String url, Map<String, String> params) {
        StringBuilder paramStr = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (first) {
                first = false;
                paramStr.append("?");
            } else {
                paramStr.append("&");
            }
            paramStr.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return url + paramStr.toString();
    }

    /**
     * 設(shè)置請(qǐng)求頭
     */
    private static void setHeaders(HttpRequestBase request, Map<String, String> headers) {
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            request.setHeader(entry.getKey(), entry.getValue());
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 1、Activity生命周期? onCreate() -> onStart() -> onResume() -> ...
    tgcity閱讀 988評(píng)論 0 4
  • 1、java中==和equals和hashCode的區(qū)別 基本數(shù)據(jù)類型的==比較的值相等.類的==比較的內(nèi)存的地址...
    Mr_Fly閱讀 1,039評(píng)論 0 0
  • 1、java中==和equals和hashCode的區(qū)別 基本數(shù)據(jù)類型的==比較的值相等.類的==比較的內(nèi)存的地址...
    快感的感知閱讀 1,181評(píng)論 0 4
  • Android中高級(jí)面試題 1、Activity生命周期? onCreate() -> onStart() -> ...
    司文喰閱讀 588評(píng)論 0 0

友情鏈接更多精彩內(nèi)容