HTTP客戶端連線,選擇HttpClient還是OkHttp?

何甜甜在嗎發表於2020-01-08

寫在前面

為什麼會寫這篇文章,起因於和朋友的聊天

HTTP客戶端連線,選擇HttpClient還是OkHttp?
這又觸及到我的知識盲區了,首先來一波面向百度學習,直接根據關鍵字httpclient和okhttp的區別、效能比較進行搜尋,沒有找到想要的答案,於是就去overstackflow上看看是不是有人問過這個問題,果然不會讓你失望的
HTTP客戶端連線,選擇HttpClient還是OkHttp?
所以從使用、效能、超時配置方面進行比較

使用

HttpClient和OkHttp一般用於呼叫其它服務,一般服務暴露出來的介面都為http,http常用請求型別就為GET、PUT、POST和DELETE,因此主要介紹這些請求型別的呼叫

HttpClient使用介紹

使用HttpClient傳送請求主要分為一下幾步驟:

  • 建立 CloseableHttpClient物件或CloseableHttpAsyncClient物件,前者同步,後者為非同步
  • 建立Http請求物件
  • 呼叫execute方法執行請求,如果是非同步請求在執行之前需呼叫start方法

建立連線:

CloseableHttpClient httpClient = HttpClientBuilder.create().build();
複製程式碼

該連線為同步連線

GET請求:

@Test
public void testGet() throws IOException {
    String api = "/api/files/1";
    String url = String.format("%s%s", BASE_URL, api);
    HttpGet httpGet = new HttpGet(url);
    CloseableHttpResponse response = httpClient.execute(httpGet);
    System.out.println(EntityUtils.toString(response.getEntity()));
}
複製程式碼

使用HttpGet表示該連線為GET請求,HttpClient呼叫execute方法傳送GET請求

PUT請求:

@Test
public void testPut() throws IOException {
    String api = "/api/user";
    String url = String.format("%s%s", BASE_URL, api);
    HttpPut httpPut = new HttpPut(url);
    UserVO userVO = UserVO.builder().name("h2t").id(16L).build();
    httpPut.setHeader("Content-Type", "application/json;charset=utf8");
    httpPut.setEntity(new StringEntity(JSONObject.toJSONString(userVO), "UTF-8"));
    CloseableHttpResponse response = httpClient.execute(httpPut);
    System.out.println(EntityUtils.toString(response.getEntity()));
}
複製程式碼

POST請求:

  • 新增物件
    @Test
    public void testPost() throws IOException {
        String api = "/api/user";
        String url = String.format("%s%s", BASE_URL, api);
        HttpPost httpPost = new HttpPost(url);
        UserVO userVO = UserVO.builder().name("h2t2").build();
        httpPost.setHeader("Content-Type", "application/json;charset=utf8");
        httpPost.setEntity(new StringEntity(JSONObject.toJSONString(userVO), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        System.out.println(EntityUtils.toString(response.getEntity()));
    }
    複製程式碼
    該請求是一個建立物件的請求,需要傳入一個json字串
  • 上傳檔案
    @Test
    public void testUpload1() throws IOException {
        String api = "/api/files/1";
        String url = String.format("%s%s", BASE_URL, api);
        HttpPost httpPost = new HttpPost(url);
        File file = new File("C:/Users/hetiantian/Desktop/學習/docker_practice.pdf");
        FileBody fileBody = new FileBody(file);
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        builder.addPart("file", fileBody);  //addPart上傳檔案
        HttpEntity entity = builder.build();
        httpPost.setEntity(entity);
        CloseableHttpResponse response = httpClient.execute(httpPost);
        System.out.println(EntityUtils.toString(response.getEntity()));
    }
    複製程式碼
    通過addPart上傳檔案

DELETE請求:

@Test
public void testDelete() throws IOException {
    String api = "/api/user/12";
    String url = String.format("%s%s", BASE_URL, api);
    HttpDelete httpDelete = new HttpDelete(url);
    CloseableHttpResponse response = httpClient.execute(httpDelete);
    System.out.println(EntityUtils.toString(response.getEntity()));
}
複製程式碼

請求的取消:

@Test
public void testCancel() throws IOException {
    String api = "/api/files/1";
    String url = String.format("%s%s", BASE_URL, api);
    HttpGet httpGet = new HttpGet(url);
    httpGet.setConfig(requestConfig);  //設定超時時間
    //測試連線的取消

    long begin = System.currentTimeMillis();
    CloseableHttpResponse response = httpClient.execute(httpGet);
    while (true) {
        if (System.currentTimeMillis() - begin > 1000) {
          httpGet.abort();
          System.out.println("task canceled");
          break;
      }
    }

    System.out.println(EntityUtils.toString(response.getEntity()));
}
複製程式碼

呼叫abort方法取消請求 執行結果:

task canceled
cost 8098 msc
Disconnected from the target VM, address: '127.0.0.1:60549', transport: 'socket'

java.net.SocketException: socket closed...【省略】
複製程式碼
OkHttp使用

使用OkHttp傳送請求主要分為一下幾步驟:

  • 建立OkHttpClient物件
  • 建立Request物件
  • 將Request 物件封裝為Call
  • 通過Call 來執行同步或非同步請求,呼叫execute方法同步執行,呼叫enqueue方法非同步執行

建立連線:

private OkHttpClient client = new OkHttpClient();
複製程式碼

GET請求:

@Test
public void testGet() throws IOException {
    String api = "/api/files/1";
    String url = String.format("%s%s", BASE_URL, api);
    Request request = new Request.Builder()
            .url(url)
            .get() 
            .build();
    final Call call = client.newCall(request);
    Response response = call.execute();
    System.out.println(response.body().string());
}
複製程式碼

PUT請求:

@Test
public void testPut() throws IOException {
    String api = "/api/user";
    String url = String.format("%s%s", BASE_URL, api);
    //請求引數
    UserVO userVO = UserVO.builder().name("h2t").id(11L).build();
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
    JSONObject.toJSONString(userVO));
    Request request = new Request.Builder()
            .url(url)
            .put(requestBody)
            .build();
    final Call call = client.newCall(request);
    Response response = call.execute();
    System.out.println(response.body().string());
}
複製程式碼

POST請求:

  • 新增物件

    @Test
    public void testPost() throws IOException {
        String api = "/api/user";
        String url = String.format("%s%s", BASE_URL, api);
        //請求引數
        JSONObject json = new JSONObject();
        json.put("name", "hetiantian");
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),     String.valueOf(json));
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody) //post請求
               .build();
        final Call call = client.newCall(request);
        Response response = call.execute();
        System.out.println(response.body().string());
    }
    複製程式碼
  • 上傳檔案

    @Test
    public void testUpload() throws IOException {
        String api = "/api/files/1";
        String url = String.format("%s%s", BASE_URL, api);
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", "docker_practice.pdf",
                        RequestBody.create(MediaType.parse("multipart/form-data"),
                                new File("C:/Users/hetiantian/Desktop/學習/docker_practice.pdf")))
                .build();
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)  //預設為GET請求,可以不寫
                .build();
        final Call call = client.newCall(request);
        Response response = call.execute();
        System.out.println(response.body().string());
    }
    複製程式碼

    通過addFormDataPart方法模擬表單方式上傳檔案

DELETE請求:

@Test
public void testDelete() throws IOException {
  String url = String.format("%s%s", BASE_URL, api);
  //請求引數
  Request request = new Request.Builder()
          .url(url)
          .delete()
          .build();
  final Call call = client.newCall(request);
  Response response = call.execute();
  System.out.println(response.body().string());
}
複製程式碼

請求的取消:

@Test
public void testCancelSysnc() throws IOException {
    String api = "/api/files/1";
    String url = String.format("%s%s", BASE_URL, api);
    Request request = new Request.Builder()
            .url(url)
            .get()  
            .build();
    final Call call = client.newCall(request);
    Response response = call.execute();
    long start = System.currentTimeMillis();
    //測試連線的取消
    while (true) {
         //1分鐘獲取不到結果就取消請求
        if (System.currentTimeMillis() - start > 1000) {
            call.cancel();
            System.out.println("task canceled");
            break;
        }
    }

    System.out.println(response.body().string());
}
複製程式碼

呼叫cancel方法進行取消 測試結果:

task canceled
cost 9110 msc

java.net.SocketException: socket closed...【省略】
複製程式碼
小結
  • OkHttp使用build模式建立物件來的更簡潔一些,並且使用.post/.delete/.put/.get方法表示請求型別,不需要像HttpClient建立HttpGet、HttpPost等這些方法來建立請求型別
  • 依賴包上,如果HttpClient需要傳送非同步請求、實現檔案上傳,需要額外的引入非同步請求依賴
     <!---檔案上傳-->
     <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpmime</artifactId>
         <version>4.5.3</version>
     </dependency>
     <!--非同步請求-->
     <dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpasyncclient</artifactId>
         <version>4.5.3</version>
     </dependency>
    複製程式碼
  • 請求的取消,HttpClient使用abort方法,OkHttp使用cancel方法,都挺簡單的,如果使用的是非同步client,則在丟擲異常時呼叫取消請求的方法即可

超時設定

HttpClient超時設定:
在HttpClient4.3+版本以上,超時設定通過RequestConfig進行設定

private CloseableHttpClient httpClient = HttpClientBuilder.create().build();
private RequestConfig requestConfig =  RequestConfig.custom()
        .setSocketTimeout(60 * 1000)
        .setConnectTimeout(60 * 1000).build();
String api = "/api/files/1";
String url = String.format("%s%s", BASE_URL, api);
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);  //設定超時時間
複製程式碼

超時時間是設定在請求型別HttpGet上,而不是HttpClient上

OkHttp超時設定:
直接在OkHttp上進行設定

private OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)//設定連線超時時間
        .readTimeout(60, TimeUnit.SECONDS)//設定讀取超時時間
        .build();
複製程式碼

小結:
如果client是單例模式,HttpClient在設定超時方面來的更靈活,針對不同請求型別設定不同的超時時間,OkHttp一旦設定了超時時間,所有請求型別的超時時間也就確定

HttpClient和OkHttp效能比較

測試環境:

  • CPU 六核
  • 記憶體 8G
  • windows10

每種測試用例都測試五次,排除偶然性

client連線為單例:

HTTP客戶端連線,選擇HttpClient還是OkHttp?
client連線不為單例:
HTTP客戶端連線,選擇HttpClient還是OkHttp?
單例模式下,HttpClient的響應速度要更快一些,單位為毫秒,效能差異相差不大 非單例模式下,OkHttp的效能更好,HttpClient建立連線比較耗時,因為多數情況下這些資源都會寫成單例模式,因此圖一的測試結果更具有參考價值

總結

OkHttp和HttpClient在效能和使用上不分伯仲,根據實際業務選擇即可
最後附:示例程式碼,歡迎forkstar*
好久沒有對外輸出文章了

HTTP客戶端連線,選擇HttpClient還是OkHttp?
主要是寫的前兩篇沒有人看,受打擊了,急需網友的肯定【點贊呀
HTTP客戶端連線,選擇HttpClient還是OkHttp?

相關文章