• Apache HttpClient 5 使用详细教程


    HTTP Components Logo

    超文本传输协议(HTTP)可能是当今互联网上最重要的协议之一,Web 服务、微服务以及支持网络的各种设备上的服务几乎都是 HTTP 协议,HTTP 协议已经从 Web 浏览器走向了更广泛的使用场景。

    虽然 java.net 包已经提供了 HTTP 访问资源的基本功能,但是它不够灵活,而且不能随心所欲的进行自定义。Apache HttpClient 5 是一个开源的 HTTP 工具包,可以支持最新 HTTP 协议标准,且有丰富的 API 和强大的扩展特性,可以用于构建任何需要进行 HTTP 协议处理的应用程序。

    这篇文章介绍 Apache HttpClient 5 中最为常见的一些用法,通过这篇文章可以快速的入门使用 HttpClient 5,主要内容包括 HttpClient 5 的 Get 请求、Post 请求、如何携带参数、JSON 参数、设置超时、异步请求、操作 Cookie、表单登录、基本认证、Digest 认证以及自定义 HTTP 请求拦截器等。

    HttpClient 5 依赖

    HttpClient 5 Maven 依赖

    1. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 -->
    2. <dependency>
    3.     <groupId>org.apache.httpcomponents.client5</groupId>
    4.     <artifactId>httpclient5</artifactId>
    5.     <version>5.1.3</version>
    6. </dependency>
    7. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent -->
    8. <dependency>
    9.     <groupId>org.apache.httpcomponents.client5</groupId>
    10.     <artifactId>httpclient5-fluent</artifactId>
    11.     <version>5.1.3</version>
    12. </dependency>

    HttpClient 5 Gradle 依赖

    1. implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
    2. implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.1.3'

    HttpClient 5 GET 请求

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import org.apache.hc.client5.http.classic.methods.HttpGet;
    4. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    5. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    6. import org.apache.hc.client5.http.impl.classic.HttpClients;
    7. import org.apache.hc.core5.http.HttpEntity;
    8. import org.apache.hc.core5.http.ParseException;
    9. import org.apache.hc.core5.http.io.entity.EntityUtils;
    10. /**
    11.  * @author https://www.wdbyte.com
    12.  */
    13. public class HttpClient5Get {
    14.     public static void main(String[] args) {
    15.         String result = get("http://httpbin.org/get");
    16.         System.out.println(result);
    17.     }
    18.     public static String get(String url) {
    19.         String resultContent = null;
    20.         HttpGet httpGet = new HttpGet(url);
    21.         try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    22.             try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
    23.                 // 获取状态码
    24.                 System.out.println(response.getVersion()); // HTTP/1.1
    25.                 System.out.println(response.getCode()); // 200
    26.                 System.out.println(response.getReasonPhrase()); // OK
    27.                 HttpEntity entity = response.getEntity();
    28.                 // 获取响应信息
    29.                 resultContent = EntityUtils.toString(entity);
    30.             }
    31.         } catch (IOException | ParseException e) {
    32.             e.printStackTrace();
    33.         }
    34.         return resultContent;
    35.     }
    36. }

    响应信息:

    1. HTTP/1.1
    2. 200
    3. OK
    4. {
    5.   "args": {}, 
    6.   "headers": {
    7.     "Accept-Encoding""gzip, x-gzip, deflate"
    8.     "Host""httpbin.org"
    9.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    10.     "X-Amzn-Trace-Id""Root=1-62bb1891-5ab5e5376ed960471bf32f17"
    11.   }, 
    12.   "origin""47.251.4.198"
    13.   "url""http://httpbin.org/get"
    14. }

    HttpClient 5 Fluent GET

    使用 Apache HttpClient 5 提供的 Fluent API 可以更便捷的发起 GET 请求,但是可操作的地方较少。

    依赖:

    1. <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent -->
    2. <dependency>
    3.     <groupId>org.apache.httpcomponents.client5</groupId>
    4.     <artifactId>httpclient5-fluent</artifactId>
    5.     <version>5.1.3</version>
    6. </dependency>

    示例:

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import org.apache.hc.client5.http.fluent.Request;
    4. import org.apache.hc.client5.http.fluent.Response;
    5. /**
    6. @author https://www.wdbyte.com
    7.  */
    8. public class HttpClient5GetFluent {
    9.     public static void main(String[] args) {
    10.         System.out.println(get("http://httpbin.org/get"));
    11.     }
    12.     public static String get(String url) {
    13.         String result = null;
    14.         try {
    15.             Response response = Request.get(url).execute();
    16.             result = response.returnContent().asString();
    17.         } catch (IOException e) {
    18.             e.printStackTrace();
    19.         }
    20.         return result;
    21.     }
    22. }

    输出信息:

    1. {
    2.   "args": {}, 
    3.   "headers": {
    4.     "Accept-Encoding""gzip, x-gzip, deflate"
    5.     "Host""httpbin.org"
    6.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    7.     "X-Amzn-Trace-Id""Root=1-62bb190e-1ba46a92645843a04c55da32"
    8.   }, 
    9.   "origin""47.251.4.198"
    10.   "url""http://httpbin.org/get"
    11. }

    HttpClient5 GET 请求参数

    使用 URIBuilder 的 addParameters() 方法来构建 GET 请求的参数。

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import java.net.URI;
    4. import java.net.URISyntaxException;
    5. import java.util.ArrayList;
    6. import java.util.List;
    7. import org.apache.hc.client5.http.classic.methods.HttpGet;
    8. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    9. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    10. import org.apache.hc.client5.http.impl.classic.HttpClients;
    11. import org.apache.hc.core5.http.HttpEntity;
    12. import org.apache.hc.core5.http.NameValuePair;
    13. import org.apache.hc.core5.http.ParseException;
    14. import org.apache.hc.core5.http.io.entity.EntityUtils;
    15. import org.apache.hc.core5.http.message.BasicNameValuePair;
    16. import org.apache.hc.core5.net.URIBuilder;
    17. /**
    18. * @author https://www.wdbyte.com
    19.  */
    20. public class HttpClient5GetParams {
    21.     public static void main(String[] args) {
    22.         String result = get("http://httpbin.org/get");
    23.         System.out.println(result);
    24.     }
    25.     public static String get(String url) {
    26.         String resultContent = null;
    27.         HttpGet httpGet = new HttpGet(url);
    28.         // 表单参数
    29.         List<NameValuePair> nvps = new ArrayList<>();
    30.         // GET 请求参数
    31.         nvps.add(new BasicNameValuePair("username""wdbyte.com"));
    32.         nvps.add(new BasicNameValuePair("password""secret"));
    33.         // 增加到请求 URL 中
    34.         try {
    35.             URI uri = new URIBuilder(new URI(url))
    36.                 .addParameters(nvps)
    37.                 .build();
    38.             httpGet.setUri(uri);
    39.         } catch (URISyntaxException e) {
    40.             throw new RuntimeException(e);
    41.         }
    42.         try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    43.             try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
    44.                 // 获取状态码
    45.                 System.out.println(response.getVersion()); // HTTP/1.1
    46.                 System.out.println(response.getCode()); // 200
    47.                 System.out.println(response.getReasonPhrase()); // OK
    48.                 HttpEntity entity = response.getEntity();
    49.                 // 获取响应信息
    50.                 resultContent = EntityUtils.toString(entity);
    51.             }
    52.         } catch (IOException | ParseException e) {
    53.             e.printStackTrace();
    54.         }
    55.         return resultContent;
    56.     }
    57. }

    输出信息:

    1. {
    2.   "args": {
    3.     "password""secret"
    4.     "username""wdbyte.com"
    5.   }, 
    6.   "headers": {
    7.     "Accept-Encoding""gzip, x-gzip, deflate"
    8.     "Host""httpbin.org"
    9.     "User-Agent""Apache-HttpClient/5.1.3 (Java/1.8.0_151)"
    10.     "X-Amzn-Trace-Id""Root=1-62ecc660-69d58a226aefb1b6226541ec"
    11.   }, 
    12.   "origin""42.120.75.185"
    13.   "url""http://httpbin.org/get?username=wdbyte.com&password=secret"
    14. }

    下面是通过抓包得到的请求响应信息格式:

    1. // 请求信息
    2. GET /get?username=wdbyte.com&password=secret HTTP/1.1
    3. Accept-Encoding: gzip, x-gzip, deflate
    4. Host: httpbin.org
    5. Connection: keep-alive
    6. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    7. // 响应信息
    8. HTTP/1.1 200 OK
    9. Date: Fri, 05 Aug 2022 07:27:30 GMT
    10. Content-Type: application/json
    11. Content-Length: 405
    12. Connection: keep-alive
    13. Server: gunicorn/19.9.0
    14. Access-Control-Allow-Origin: *
    15. Access-Control-Allow-Credentials: true
    16. {
    17.   "args": {
    18.     "password""secret"
    19.     "username""wdbyte.com"
    20.   }, 
    21.   "headers": {
    22.     "Accept-Encoding""gzip, x-gzip, deflate"
    23.     "Host""httpbin.org"
    24.     "User-Agent""Apache-HttpClient/5.1.3 (Java/1.8.0_151)"
    25.     "X-Amzn-Trace-Id""Root=1-62ecc660-69d58a226aefb1b6226541ec"
    26.   }, 
    27.   "origin""42.120.75.185"
    28.   "url""http://httpbin.org/get?username=wdbyte.com&password=secret"
    29. }

    HttpClient 5 POST 请求

    下面演示发起一个 POST 请求,并携带表单参数。

    参数:username=wdbyte.com&password=secret

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import java.util.ArrayList;
    4. import java.util.List;
    5. import org.apache.hc.client5.http.classic.methods.HttpPost;
    6. import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
    7. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    8. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    9. import org.apache.hc.client5.http.impl.classic.HttpClients;
    10. import org.apache.hc.core5.http.HttpEntity;
    11. import org.apache.hc.core5.http.NameValuePair;
    12. import org.apache.hc.core5.http.ParseException;
    13. import org.apache.hc.core5.http.io.entity.EntityUtils;
    14. import org.apache.hc.core5.http.message.BasicNameValuePair;
    15. /**
    16. * @author https://www.wdbyte.com
    17.  */
    18. public class HttpClient5Post {
    19.     public static void main(String[] args) {
    20.         String result = post("http://httpbin.org/post");
    21.         System.out.println(result);
    22.     }
    23.     public static String post(String url) {
    24.         String result = null;
    25.         HttpPost httpPost = new HttpPost(url);
    26.         // 表单参数
    27.         List<NameValuePair> nvps = new ArrayList<>();
    28.         // POST 请求参数
    29.         nvps.add(new BasicNameValuePair("username""wdbyte.com"));
    30.         nvps.add(new BasicNameValuePair("password""secret"));
    31.         httpPost.setEntity(new UrlEncodedFormEntity(nvps));
    32.         try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    33.             try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
    34.                 System.out.println(response.getVersion()); // HTTP/1.1
    35.                 System.out.println(response.getCode()); // 200
    36.                 System.out.println(response.getReasonPhrase()); // OK
    37.                 HttpEntity entity = response.getEntity();
    38.                 // 获取响应信息
    39.                 result = EntityUtils.toString(entity);
    40.                 // 确保流被完全消费
    41.                 EntityUtils.consume(entity);
    42.             }
    43.         } catch (IOException | ParseException e) {
    44.             e.printStackTrace();
    45.         }
    46.         return result;
    47.     }
    48. }

    输出信息:

    1. HTTP/1.1
    2. 200
    3. OK
    4. {
    5.   "args": {}, 
    6.   "data"""
    7.   "files": {}, 
    8.   "form": {
    9.     "password""secret"
    10.     "username""wdbyte.com"
    11.   }, 
    12.   "headers": {
    13.     "Accept-Encoding""gzip, x-gzip, deflate"
    14.     "Content-Length""35"
    15.     "Content-Type""application/x-www-form-urlencoded; charset=ISO-8859-1"
    16.     "Host""httpbin.org"
    17.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    18.     "X-Amzn-Trace-Id""Root=1-62bb1ac8-489b2100728c81d70797a482"
    19.   }, 
    20.   "json"null
    21.   "origin""183.128.136.89"
    22.   "url""http://httpbin.org/post"
    23. }

    下面是通过 Wireshark 抓包得到的请求信息:

    1. POST /post HTTP/1.1
    2. Accept-Encoding: gzip, x-gzip, deflate
    3. Content-Length: 35
    4. Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1
    5. Host: httpbin.org
    6. Connection: keep-alive
    7. User-Agent: Apache-HttpClient/5.1.3 (Java/17)
    8. username=wdbyte.com&password=secret

    HttpClient 5 Fluent POST

    使用 Apache HttpClient 5 提供的 Fluent API 可以更便捷的发起 POST 请求,但是可操作的地方较少。

    一样发送一个简单的表单参数:username=wdbyte.com&password=secret

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import org.apache.hc.client5.http.fluent.Request;
    4. import org.apache.hc.core5.http.message.BasicNameValuePair;
    5. /**
    6. @author https://www.wdbyte.com
    7.  */
    8. public class HttpClient5PostFluent {
    9.     public static void main(String[] args) {
    10.         String result = post("http://httpbin.org/post");
    11.         System.out.println(result);
    12.     }
    13.     public static String post(String url) {
    14.         String result = null;
    15.         Request request = Request.post(url);
    16.         // POST 请求参数
    17.         request.bodyForm(
    18.             new BasicNameValuePair("username""wdbyte.com"),
    19.             new BasicNameValuePair("password""secret"));
    20.         try {
    21.             result = request.execute().returnContent().asString();
    22.         } catch (IOException e) {
    23.             e.printStackTrace();
    24.         }
    25.         return result;
    26.     }
    27. }

    输出信息:

    1. {
    2.   "args": {}, 
    3.   "data"""
    4.   "files": {}, 
    5.   "form": {
    6.     "password""secret"
    7.     "username""wdbyte.com"
    8.   }, 
    9.   "headers": {
    10.     "Accept-Encoding""gzip, x-gzip, deflate"
    11.     "Content-Length""35"
    12.     "Content-Type""application/x-www-form-urlencoded; charset=ISO-8859-1"
    13.     "Host""httpbin.org"
    14.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    15.     "X-Amzn-Trace-Id""Root=1-62bb1c8a-7aee8c004f06919f31a2b533"
    16.   }, 
    17.   "json"null
    18.   "origin""183.128.136.89"
    19.   "url""http://httpbin.org/post"
    20. }

    HttpClient5 POST JSON 参数

    使用 StringEntity 类存入 JSON 参数。

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import org.apache.hc.client5.http.classic.methods.HttpPost;
    4. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    5. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    6. import org.apache.hc.client5.http.impl.classic.HttpClients;
    7. import org.apache.hc.core5.http.ParseException;
    8. import org.apache.hc.core5.http.io.entity.EntityUtils;
    9. import org.apache.hc.core5.http.io.entity.StringEntity;
    10. /**
    11. * @author https://www.wdbyte.com
    12.  */
    13. public class HttpClient5PostWithJson {
    14.     public static void main(String[] args) {
    15.         String json = "{"
    16.             + "    \"password\": \"secret\","
    17.             + "    \"username\": \"wdbyte.com\""
    18.             + "}";
    19.         String result = post("http://httpbin.org/post", json);
    20.         System.out.println(result);
    21.     }
    22.     public static String post(String url, String jsonBody) {
    23.         String result = null;
    24.         HttpPost httpPost = new HttpPost(url);
    25.         httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
    26.       
    27.         try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    28.             try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
    29.                 // 获取响应信息
    30.                 result = EntityUtils.toString(response.getEntity());
    31.             }
    32.         } catch (IOException | ParseException e) {
    33.             e.printStackTrace();
    34.         }
    35.         return result;
    36.     }
    37. }

    输出信息:

    1. {
    2.   "args": {}, 
    3.   "data""{    \"password\"\"secret\",    \"username\"\"wdbyte.com\"}"
    4.   "files": {}, 
    5.   "form": {}, 
    6.   "headers": {
    7.     "Accept-Encoding""gzip, x-gzip, deflate"
    8.     "Content-Length""55"
    9.     "Content-Type""text/plain; charset=ISO-8859-1"
    10.     "Host""httpbin.org"
    11.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    12.     "X-Amzn-Trace-Id""Root=1-62bb1dbb-5a963c1d798b06be3ee1a15e"
    13.   }, 
    14.   "json": {
    15.     "password""secret"
    16.     "username""wdbyte.com"
    17.   }, 
    18.   "origin""183.128.136.89"
    19.   "url""http://httpbin.org/post"
    20. }

    下面是通过 Wireshark 抓包得到的请求响应信息:

    1. // 请求信息
    2. POST /post HTTP/1.1
    3. Accept-Encoding: gzip, x-gzip, deflate
    4. Content-Length: 55
    5. Content-Type: application/json; charset=UTF-8
    6. Host: httpbin.org
    7. Connection: keep-alive
    8. User-Agent: Apache-HttpClient/5.1.3 (Java/17)
    9. {    "password""secret",    "username""wdbyte.com"}
    10. // 响应信息
    11. HTTP/1.1 200 OK
    12. Date: Tue, 28 Jun 2022 15:30:17 GMT
    13. Content-Type: application/json
    14. Content-Length: 573
    15. Connection: keep-alive
    16. Server: gunicorn/19.9.0
    17. Access-Control-Allow-Origin: *
    18. Access-Control-Allow-Credentials: true
    19. {
    20.   "args": {}, 
    21.   "data""{    \"password\": \"secret\",    \"username\": \"wdbyte.com\"}"
    22.   "files": {}, 
    23.   "form": {}, 
    24.   "headers": {
    25.     "Accept-Encoding""gzip, x-gzip, deflate"
    26.     "Content-Length""55"
    27.     "Content-Type""application/json; charset=UTF-8"
    28.     "Host""httpbin.org"
    29.     "User-Agent""Apache-HttpClient/5.1.3 (Java/17)"
    30.     "X-Amzn-Trace-Id""Root=1-62bb1e89-64db55730a0361c720232ccd"
    31.   }, 
    32.   "json": {
    33.     "password""secret"
    34.     "username""wdbyte.com"
    35.   }, 
    36.   "origin""183.128.136.89"
    37.   "url""http://httpbin.org/post"
    38. }

    HttpClient 5 设置超时

    使用 RequestConfig 对象来配置超时时间。

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import org.apache.hc.client5.http.classic.methods.HttpGet;
    4. import org.apache.hc.client5.http.config.RequestConfig;
    5. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    6. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    7. import org.apache.hc.client5.http.impl.classic.HttpClients;
    8. import org.apache.hc.core5.http.HttpEntity;
    9. import org.apache.hc.core5.http.ParseException;
    10. import org.apache.hc.core5.http.io.entity.EntityUtils;
    11. import org.apache.hc.core5.util.Timeout;
    12. /**
    13. * @author https://www.wdbyte.com
    14.  */
    15. public class HttpClient5GetWithTimeout {
    16.     public static void main(String[] args) {
    17.         String result = get("http://httpbin.org/get");
    18.         System.out.println(result);
    19.     }
    20.     public static String get(String url) {
    21.         String resultContent = null;
    22.         // 设置超时时间
    23.         RequestConfig config = RequestConfig.custom()
    24.             .setConnectTimeout(Timeout.ofMilliseconds(5000L))
    25.             .setConnectionRequestTimeout(Timeout.ofMilliseconds(5000L))
    26.             .setResponseTimeout(Timeout.ofMilliseconds(5000L))
    27.             .build();
    28.         // 请求级别的超时
    29.         HttpGet httpGet = new HttpGet(url);
    30.         //httpGet.setConfig(config);
    31.         //try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
    32.         // 客户端级别的超时
    33.         try (CloseableHttpClient httpclient = HttpClients.custom().setDefaultRequestConfig(config).build()) {
    34.             try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
    35.                 // 获取状态码
    36.                 System.out.println(response.getVersion()); // HTTP/1.1
    37.                 System.out.println(response.getCode()); // 200
    38.                 System.out.println(response.getReasonPhrase()); // OK
    39.                 HttpEntity entity = response.getEntity();
    40.                 // 获取响应信息
    41.                 resultContent = EntityUtils.toString(entity);
    42.             }
    43.         } catch (IOException | ParseException e) {
    44.             e.printStackTrace();
    45.         }
    46.         return resultContent;
    47.     }
    48. }

    HttpClient 5 异步请求

    下面演示三种 HttpClient 5 异步请求方式。

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import java.nio.CharBuffer;
    4. import java.util.concurrent.CountDownLatch;
    5. import java.util.concurrent.ExecutionException;
    6. import java.util.concurrent.Future;
    7. import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer;
    8. import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
    9. import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
    10. import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
    11. import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
    12. import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
    13. import org.apache.hc.core5.concurrent.FutureCallback;
    14. import org.apache.hc.core5.http.ContentType;
    15. import org.apache.hc.core5.http.HttpException;
    16. import org.apache.hc.core5.http.HttpResponse;
    17. import org.apache.hc.core5.http.nio.AsyncRequestProducer;
    18. import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
    19. /**
    20.  * HttpClient 5 异步请求
    21. * @author https://www.wdbyte.com
    22.  * @date 2022/06/25
    23.  */
    24. public class HttpClient5Async {
    25.     public static void main(String[] args) {
    26.         getAsync1("http://httpbin.org/get");
    27.         getAsync2("http://httpbin.org/get");
    28.         getAsync3("http://httpbin.org/get");
    29.     }
    30.     /**
    31.      * 异步请求
    32.      *
    33.      * @param url
    34.      * @return
    35.      */
    36.     public static String getAsync1(String url) {
    37.         try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
    38.             // 开始 http clinet
    39.             httpclient.start();
    40.             // 执行请求
    41.             SimpleHttpRequest request1 = SimpleHttpRequests.get(url);
    42.             Future<SimpleHttpResponse> future = httpclient.execute(request1null);
    43.             // 等待直到返回完毕
    44.             SimpleHttpResponse response1 = future.get();
    45.             System.out.println("getAsync1:" + request1.getRequestUri() + "->" + response1.getCode());
    46.         } catch (IOException | ExecutionException | InterruptedException e) {
    47.             throw new RuntimeException(e);
    48.         }
    49.         return null;
    50.     }
    51.     /**
    52.      * 异步请求,根据响应情况回调
    53.      *
    54.      * @param url
    55.      * @return
    56.      */
    57.     public static String getAsync2(String url) {
    58.         try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
    59.             // 开始 http clinet
    60.             httpclient.start();
    61.             // 根据请求响应情况进行回调操作
    62.             CountDownLatch latch = new CountDownLatch(1);
    63.             SimpleHttpRequest request = SimpleHttpRequests.get(url);
    64.             httpclient.execute(request, new FutureCallback<SimpleHttpResponse>() {
    65.                 @Override
    66.                 public void completed(SimpleHttpResponse response2) {
    67.                     latch.countDown();
    68.                     System.out.println("getAsync2:" + request.getRequestUri() + "->" + response2.getCode());
    69.                 }
    70.                 @Override
    71.                 public void failed(Exception ex) {
    72.                     latch.countDown();
    73.                     System.out.println("getAsync2:" + request.getRequestUri() + "->" + ex);
    74.                 }
    75.                 @Override
    76.                 public void cancelled() {
    77.                     latch.countDown();
    78.                     System.out.println("getAsync2:" + request.getRequestUri() + " cancelled");
    79.                 }
    80.             });
    81.             latch.await();
    82.         } catch (IOException | InterruptedException e) {
    83.             throw new RuntimeException(e);
    84.         }
    85.         return null;
    86.     }
    87.     /**
    88.      * 异步请求,对响应流做点什么
    89.      *
    90.      * @param url
    91.      * @return
    92.      */
    93.     public static String getAsync3(String url) {
    94.         try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
    95.             // 开始 http clinet
    96.             httpclient.start();
    97.             // 根据请求响应情况进行回调操作
    98.             SimpleHttpRequest request = SimpleHttpRequests.get(url);
    99.             CountDownLatch latch = new CountDownLatch(1);
    100.             AsyncRequestProducer producer = AsyncRequestBuilder.get("http://httpbin.org/get").build();
    101.             AbstractCharResponseConsumer<HttpResponse> consumer3 = new AbstractCharResponseConsumer<HttpResponse>() {
    102.                 HttpResponse response;
    103.                 @Override
    104.                 protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {
    105.                     System.out.println("getAsync3: 开始响应....");
    106.                     this.response = response;
    107.                 }
    108.                 @Override
    109.                 protected int capacityIncrement() {
    110.                     return Integer.MAX_VALUE;
    111.                 }
    112.                 @Override
    113.                 protected void data(CharBuffer databoolean endOfStream) throws IOException {
    114.                     System.out.println("getAsync3: 收到数据....");
    115.                     // Do something useful
    116.                 }
    117.                 @Override
    118.                 protected HttpResponse buildResult() throws IOException {
    119.                     System.out.println("getAsync3: 接收完毕...");
    120.                     return response;
    121.                 }
    122.                 @Override
    123.                 public void releaseResources() {
    124.                 }
    125.             };
    126.             httpclient.execute(producer, consumer3, new FutureCallback<HttpResponse>() {
    127.                 @Override
    128.                 public void completed(HttpResponse response) {
    129.                     latch.countDown();
    130.                     System.out.println("getAsync3: "+request.getRequestUri() + "->" + response.getCode());
    131.                 }
    132.                 @Override
    133.                 public void failed(Exception ex) {
    134.                     latch.countDown();
    135.                     System.out.println("getAsync3: "+request.getRequestUri() + "->" + ex);
    136.                 }
    137.                 @Override
    138.                 public void cancelled() {
    139.                     latch.countDown();
    140.                     System.out.println("getAsync3: "+request.getRequestUri() + " cancelled");
    141.                 }
    142.             });
    143.             latch.await();
    144.         } catch (IOException | InterruptedException e) {
    145.             throw new RuntimeException(e);
    146.         }
    147.         return null;
    148.     }
    149. }

    输出结果:

    1. getAsync1:/get->200
    2. getAsync2:/get->200
    3. getAsync3: 开始响应....
    4. getAsync3: 收到数据....
    5. getAsync3: 收到数据....
    6. getAsync3: 收到数据....
    7. getAsync3: 接收完毕...
    8. getAsync3/get->200

    HttpClient 5 获取 Cookie

    请求 http://httpbin.org/cookies/set/cookieName/www.wdbyte.com 的响应中会带有一个Cookie 信息,其中 name 为 cookieName,value 为 www.wdbyte.com,我们以此用作测试。

    Postman 请求测试,可以看到响应了 Cookie 信息。

     

    下面编写 Java 代码进行请求测试

    1. package com.wdbyte.httpclient;
    2. import java.util.List;
    3. import org.apache.hc.client5.http.classic.methods.HttpGet;
    4. import org.apache.hc.client5.http.cookie.BasicCookieStore;
    5. import org.apache.hc.client5.http.cookie.Cookie;
    6. import org.apache.hc.client5.http.cookie.CookieStore;
    7. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    8. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    9. import org.apache.hc.client5.http.impl.classic.HttpClients;
    10. import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
    11. import org.apache.hc.client5.http.protocol.HttpClientContext;
    12. import org.apache.hc.core5.http.io.entity.EntityUtils;
    13. /**
    14.  * 这个例子演示了使用本地HTTP上下文填充, 自定义属性
    15.  */
    16. public class HttpClient5WithCookie {
    17.     public static void main(final String[] args) throws Exception {
    18.         try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
    19.             // 创建一个本地的 Cookie 存储
    20.             final CookieStore cookieStore = new BasicCookieStore();
    21.             // BasicClientCookie clientCookie = new BasicClientCookie("name""www.wdbyte.com");
    22.             // clientCookie.setDomain("http://httpbin.org/cookies");
    23.             // 过期时间
    24.             // clientCookie.setExpiryDate(new Date());
    25.             // 添加到本地 Cookie
    26.             // cookieStore.addCookie(clientCookie);
    27.             // 创建本地 HTTP 请求上下文 HttpClientContext
    28.             final HttpClientContext localContext = HttpClientContext.create();
    29.             // 绑定 cookieStore 到 localContext
    30.             localContext.setCookieStore(cookieStore);
    31.             final HttpGet httpget = new HttpGet("http://httpbin.org/cookies/set/cookieName/www.wdbyte.com");
    32.             System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri());
    33.             // 获取 Coolie 信息
    34.             try (final CloseableHttpResponse response = httpclient.execute(httpget, localContext)) {
    35.                 System.out.println("----------------------------------------");
    36.                 System.out.println(response.getCode() + " " + response.getReasonPhrase());
    37.                 final List<Cookie> cookies = cookieStore.getCookies();
    38.                 for (int i = 0; i < cookies.size(); i++) {
    39.                     System.out.println("Local cookie: " + cookies.get(i));
    40.                 }
    41.                 EntityUtils.consume(response.getEntity());
    42.             }
    43.         }
    44.     }
    45. }

    输出结果:

    1. 执行请求 GET http://httpbin.org/cookies/set/cookieName/www.wdbyte.com
    2. ----------------------------------------
    3. 200 OK
    4. Local cookie: [name: cookieName; value: www.wdbyte.com; domain: httpbin.org; path: /; expiry: null]

    HttpClient 5 读取文件内容请求

    准备一个 JSON 内容格式的文件 params.json。

    {"name":"www.wdbyte.com"}

    读取这个文件作为请求参数发起请求。

    1. package com.wdbyte.httpclient;
    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import org.apache.hc.client5.http.classic.methods.HttpPost;
    5. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    6. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    7. import org.apache.hc.client5.http.impl.classic.HttpClients;
    8. import org.apache.hc.core5.http.ContentType;
    9. import org.apache.hc.core5.http.io.entity.EntityUtils;
    10. import org.apache.hc.core5.http.io.entity.FileEntity;
    11. import org.apache.hc.core5.http.io.entity.InputStreamEntity;
    12. /**
    13.  * 加载数据流作为 POST 请求参数
    14.  */
    15. public class HttpClient5ChunkEncodedPost {
    16.     public static void main(final String[] args) throws Exception {
    17.         String params = "/Users/darcy/params.json";
    18.         try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
    19.             final HttpPost httppost = new HttpPost("http://httpbin.org/post");
    20.             final InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(params), -1,
    21.                 ContentType.APPLICATION_JSON);
    22.             // 也可以使用 FileEntity 的形式
    23.             // FileEntity reqEntity = new FileEntity(new File(params), ContentType.APPLICATION_JSON);
    24.             httppost.setEntity(reqEntity);
    25.             System.out.println("执行请求 " + httppost.getMethod() + " " + httppost.getUri());
    26.             try (final CloseableHttpResponse response = httpclient.execute(httppost)) {
    27.                 System.out.println("----------------------------------------");
    28.                 System.out.println(response.getCode() + " " + response.getReasonPhrase());
    29.                 System.out.println(EntityUtils.toString(response.getEntity()));
    30.             }
    31.         }
    32.     }
    33. }

    输出结果:

    1. 执行请求 POST http://httpbin.org/post
    2. ----------------------------------------
    3. 200 OK
    4. {
    5.   "args": {}, 
    6.   "data""{\"name\":\"www.wdbyte.com\"}\n"
    7.   "files": {}, 
    8.   "form": {}, 
    9.   "headers": {
    10.     "Accept-Encoding""gzip, x-gzip, deflate"
    11.     "Content-Length""26"
    12.     "Content-Type""application/json; charset=UTF-8"
    13.     "Host""httpbin.org"
    14.     "User-Agent""Apache-HttpClient/5.1.3 (Java/1.8.0_151)"
    15.     "X-Amzn-Trace-Id""Root=1-62ee4d95-1f956d4303cea09c52694c86"
    16.   }, 
    17.   "json": {
    18.     "name""www.wdbyte.com"
    19.   }, 
    20.   "origin""42.120.74.238"
    21.   "url""http://httpbin.org/post"
    22. }

    HttpClient 5 表单登录

    表单登录可以理解为发起一个携带了认证信息的请求,然后得到响应的 Cookie 的过程。当然这里不仅仅适用于表单登录,也可以是简单的发起一个携带了表单信息的请求。

    本应该使用 POST 请求发送表单参数测试,但是在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 请求

    示例代码:

    1. package com.wdbyte.httpclient;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. import org.apache.hc.client5.http.classic.methods.HttpGet;
    5. import org.apache.hc.client5.http.cookie.BasicCookieStore;
    6. import org.apache.hc.client5.http.cookie.Cookie;
    7. import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
    8. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    9. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    10. import org.apache.hc.client5.http.impl.classic.HttpClients;
    11. import org.apache.hc.core5.http.HttpEntity;
    12. import org.apache.hc.core5.http.NameValuePair;
    13. import org.apache.hc.core5.http.io.entity.EntityUtils;
    14. import org.apache.hc.core5.http.message.BasicNameValuePair;
    15. /**
    16.  * 演示基于表单的登录
    17.  * 
    18.  * @author https://www.wdbyte.com
    19.  */
    20. public class HttpClient5FormLogin {
    21.     public static void main(final String[] args) throws Exception {
    22.         final BasicCookieStore cookieStore = new BasicCookieStore();
    23.         try (final CloseableHttpClient httpclient = HttpClients.custom()
    24.                 .setDefaultCookieStore(cookieStore)
    25.                 .build()) {
    26.             // 本应该使用 POST 请求发送表单参数,但是在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 请求
    27.             // HttpPost httpPost = new HttpPost("http://httpbin.org/cookies/set/username/wdbyte.com");
    28.             HttpGet httpPost = new HttpGet("http://httpbin.org/cookies/set/username/wdbyte.com");
    29.             // POST 表单请求参数
    30.             List<NameValuePair> nvps = new ArrayList<>();
    31.             nvps.add(new BasicNameValuePair("username""wdbyte.com"));
    32.             nvps.add(new BasicNameValuePair("password""secret"));
    33.             httpPost.setEntity(new UrlEncodedFormEntity(nvps));
    34.             try (final CloseableHttpResponse response2 = httpclient.execute(httpPost)) {
    35.                 final HttpEntity entity = response2.getEntity();
    36.                 System.out.println("Login form get: " + response2.getCode() + " " + response2.getReasonPhrase());
    37.                 System.out.println("当前响应信息 "+EntityUtils.toString(entity));;
    38.                 System.out.println("Post 登录 Cookie:");
    39.                 final List<Cookie> cookies = cookieStore.getCookies();
    40.                 if (cookies.isEmpty()) {
    41.                     System.out.println("None");
    42.                 } else {
    43.                     for (int i = 0; i < cookies.size(); i++) {
    44.                         System.out.println("- " + cookies.get(i));
    45.                     }
    46.                 }
    47.             }
    48.         }
    49.     }
    50. }

    输出结果:

    1. Login form get: 200 OK
    2. 当前响应信息 {
    3.   "cookies": {
    4.     "username": "wdbyte.com"
    5.   }
    6. }
    7. Post 登录 Cookie:
    8. - [name: username; value: wdbyte.com; domain: httpbin.org; path: /; expiry: null]

    HttpClient 5 Basic Authorization

    HTTP 基本认证(Basic Authorization)是一种比较简单的认证实现,主要流程如下

    1. 1. 请求一个需要进行基本认证的 HTTP 接口,但是没有携带认证信息。

    2. 2. 此时会响应 401 状态码,并在响应 header 中的 WWW-Authenticate 提示需要进行基本认证。

    3. 3. 用户把需要提交认证信息进行冒号拼接,然后进行 base64 编码,再在得到的字符串开头拼接上 Basic 放入请求头 Authorization 中。

    4. 4. 认证成功,响应成功。

    你可以通过浏览器打开下面这个 URL 进行基本认证测试。

    http://httpbin.org/basic-auth/admin/123456

    在 Apache HttpClient 5 中的实现方式。

    1. package com.wdbyte.httpclient;
    2. import org.apache.hc.client5.http.auth.AuthScope;
    3. import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
    4. import org.apache.hc.client5.http.classic.methods.HttpGet;
    5. import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
    6. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    7. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    8. import org.apache.hc.client5.http.impl.classic.HttpClients;
    9. import org.apache.hc.core5.http.io.entity.EntityUtils;
    10. /**
    11.  * 一个简单的示例,它使用HttpClient执行HTTP请求;
    12.  * 一个需要进行用户身份验证的目标站点。
    13.  */
    14. public class HttpClient5BasicAuthentication {
    15.     public static void main(final String[] args) throws Exception {
    16.         final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
    17.         credsProvider.setCredentials(
    18.                 new AuthScope("httpbin.org"80),
    19.                 new UsernamePasswordCredentials("admin""123456".toCharArray()));
    20.         try (final CloseableHttpClient httpclient = HttpClients.custom()
    21.                 .setDefaultCredentialsProvider(credsProvider)
    22.                 .build()) {
    23.             final HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/admin/123456");
    24.             System.out.println("执行请求" + httpget.getMethod() + " " + httpget.getUri());
    25.             try (final CloseableHttpResponse response = httpclient.execute(httpget)) {
    26.                 System.out.println("----------------------------------------");
    27.                 System.out.println(response.getCode() + " " + response.getReasonPhrase());
    28.                 System.out.println(EntityUtils.toString(response.getEntity()));
    29.             }
    30.         }
    31.     }
    32. }

    输出结果:

    1. 执行请求GET http://httpbin.org/basic-auth/user/passwd
    2. ----------------------------------------
    3. 200 OK
    4. {
    5.   "authenticated"true
    6.   "user""user"
    7. }

    通过抓包可以看到完整的 HTTP 请求响应过程。

    1. // 请求
    2. GET /basic-auth/user/passwd HTTP/1.1
    3. Accept-Encoding: gzip, x-gzip, deflate
    4. Host: httpbin.org
    5. Connection: keep-alive
    6. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    7. // 响应
    8. HTTP/1.1 401 UNAUTHORIZED
    9. Date: Sat, 06 Aug 2022 08:25:33 GMT
    10. Content-Length: 0
    11. Connection: keep-alive
    12. Server: gunicorn/19.9.0
    13. WWW-Authenticate: Basic realm="Fake Realm"
    14. Access-Control-Allow-Origin: *
    15. Access-Control-Allow-Credentials: true
    16. // 请求
    17. GET /basic-auth/user/passwd HTTP/1.1
    18. Host: httpbin.org
    19. Connection: keep-alive
    20. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    21. Authorization: Basic dXNlcjpwYXNzd2Q=
    22. // 响应
    23. HTTP/1.1 200 OK
    24. Date: Sat, 06 Aug 2022 08:25:33 GMT
    25. Content-Type: application/json
    26. Content-Length: 47
    27. Connection: keep-alive
    28. Server: gunicorn/19.9.0
    29. Access-Control-Allow-Origin: *
    30. Access-Control-Allow-Credentials: true
    31. {
    32.   "authenticated"true
    33.   "user""user"
    34. }

    HttpClient 5 Digest Authorization

    HTTP Basic Authorization 的缺点显而易见,密码通过明文传输存在一定的安全风险,Digest Authorization 认证方式解决了明文传输的问题,这里不过多介绍 Digest 的相关内容,通过一个图简单的示意 Digest 认证方式的流程。

     

    Digest 认证流程

    下面是代码演示。

    1. package com.wdbyte.httpclient;
    2. import org.apache.hc.client5.http.auth.AuthExchange;
    3. import org.apache.hc.client5.http.auth.AuthScheme;
    4. import org.apache.hc.client5.http.auth.AuthScope;
    5. import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
    6. import org.apache.hc.client5.http.classic.methods.HttpGet;
    7. import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
    8. import org.apache.hc.client5.http.impl.auth.DigestScheme;
    9. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    10. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    11. import org.apache.hc.client5.http.impl.classic.HttpClients;
    12. import org.apache.hc.client5.http.protocol.HttpClientContext;
    13. import org.apache.hc.core5.http.HttpHost;
    14. import org.apache.hc.core5.http.io.entity.EntityUtils;
    15. /**
    16.  *
    17.  * HttpClient如何验证多个请求的示例
    18.  * 使用相同的摘要方案。在初始请求/响应交换之后
    19.  * 共享相同执行上下文的所有后续请求都可以重用
    20.  * 要向服务器进行身份验证的最后一个摘要nonce值。
    21.  */
    22. public class HttpClient5PreemptiveDigestAuthentication {
    23.     public static void main(final String[] args) throws Exception {
    24.         try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
    25.             final HttpHost target = new HttpHost("http""httpbin.org"80);
    26.             final HttpClientContext localContext = HttpClientContext.create();
    27.             final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    28.             credentialsProvider.setCredentials(
    29.                     new AuthScope(target),
    30.                     new UsernamePasswordCredentials("admin""123456".toCharArray()));
    31.             localContext.setCredentialsProvider(credentialsProvider);
    32.             final HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/admin/123456");
    33.             System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri());
    34.             for (int i = 0; i < 2; i++) {
    35.                 try (final CloseableHttpResponse response = httpclient.execute(target, httpget, localContext)) {
    36.                     System.out.println("----------------------------------------");
    37.                     System.out.println(response.getCode() + " " + response.getReasonPhrase());
    38.                     EntityUtils.consume(response.getEntity());
    39.                     final AuthExchange authExchange = localContext.getAuthExchange(target);
    40.                     if (authExchange != null) {
    41.                         final AuthScheme authScheme = authExchange.getAuthScheme();
    42.                         if (authScheme instanceof DigestScheme) {
    43.                             final DigestScheme digestScheme = (DigestScheme) authScheme;
    44.                             System.out.println("Nonce: " + digestScheme.getNonce() +
    45.                                     "; count: " + digestScheme.getNounceCount());
    46.                         }
    47.                     }
    48.                 }
    49.             }
    50.         }
    51.     }
    52. }

    通过抓包工具可以清晰的看到 2 次请求的流程,在最后一次请求中,直接共享了认证信息,没有再次的重新认证的流程。

    1. // 1. 请求
    2. GET /digest-auth/auth/admin/123456 HTTP/1.1
    3. Accept-Encoding: gzip, x-gzip, deflate
    4. Host: httpbin.org
    5. Connection: keep-alive
    6. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    7. // 2. 详情,提示认证,给出参数
    8. HTTP/1.1 401 UNAUTHORIZED
    9. Date: Fri, 12 Aug 2022 07:11:06 GMT
    10. Content-Type: text/html; charset=utf-8
    11. Content-Length: 0
    12. Connection: keep-alive
    13. Server: gunicorn/19.9.0
    14. WWW-Authenticate: Digest realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", qop="auth", opaque="64b7f68b386c3acc38131f7472aa2079", algorithm=MD5, stale=FALSE
    15. Set-Cookie: stale_after=never; Path=/
    16. Set-Cookie: fake=fake_value; Path=/
    17. Access-Control-Allow-Origin: *
    18. Access-Control-Allow-Credentials: true
    19. // 3. 参数+密码 加密后再次请求
    20. GET /digest-auth/auth/admin/123456 HTTP/1.1
    21. Host: httpbin.org
    22. Connection: keep-alive
    23. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    24. Cookie: fake=fake_value; stale_after=never
    25. Authorization: Digest username="admin", realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", uri="/digest-auth/auth/admin/123456", response="7c6726f8ac54c1ba28e19c71b2fc7338", qop=auth, nc=00000001, cnonce="2fa61501d47a9d39", algorithm=MD5, opaque="64b7f68b386c3acc38131f7472aa2079"
    26. // 4. 认证成功,响应
    27. HTTP/1.1 200 OK
    28. Date: Fri, 12 Aug 2022 07:11:08 GMT
    29. Content-Type: application/json
    30. Content-Length: 48
    31. Connection: keep-alive
    32. Server: gunicorn/19.9.0
    33. Set-Cookie: fake=fake_value; Path=/
    34. Set-Cookie: stale_after=never; Path=/
    35. Access-Control-Allow-Origin: *
    36. Access-Control-Allow-Credentials: true
    37. {
    38.   "authenticated"true
    39.   "user""admin"
    40. }
    41. // 5. 再次请求,共享了登录状态。
    42. GET /digest-auth/auth/admin/123456 HTTP/1.1
    43. Accept-Encoding: gzip, x-gzip, deflate
    44. Host: httpbin.org
    45. Connection: keep-alive
    46. User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
    47. Cookie: fake=fake_value; stale_after=never
    48. Authorization: Digest username="admin", realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", uri="/digest-auth/auth/admin/123456", response="9955ac79f6a51a876a326449447f549d", qop=auth, nc=00000002, cnonce="2fa61501d47a9d39", algorithm=MD5, opaque="64b7f68b386c3acc38131f7472aa2079"
    49. // 5. 认证成功,响应
    50. HTTP/1.1 200 OK
    51. Date: Fri, 12 Aug 2022 07:11:09 GMT
    52. Content-Type: application/json
    53. Content-Length: 48
    54. Connection: keep-alive
    55. Server: gunicorn/19.9.0
    56. Set-Cookie: fake=fake_value; Path=/
    57. Set-Cookie: stale_after=never; Path=/
    58. Access-Control-Allow-Origin: *
    59. Access-Control-Allow-Credentials: true
    60. {
    61.   "authenticated"true
    62.   "user""admin"
    63. }

    HttpClient 5 拦截器

    HttpClient 5 中的拦截器可以对请求过程的各个阶段进行拦截处理,通过 HttpClientBuilder 中的关于 Interceptor 的方法可以看到可以进行拦截的节点。

     

    HttpClient5 拦截器

    下面编写一个示例,发起三次请求,每次请求都在请求头 herader 中增加一个 request-id 参数,然后对 request-id 值为 2 的请求直接响应 404 结束。

    1. package com.wdbyte.httpclient;
    2. import java.io.IOException;
    3. import java.util.concurrent.atomic.AtomicLong;
    4. import org.apache.hc.client5.http.classic.ExecChain;
    5. import org.apache.hc.client5.http.classic.ExecChain.Scope;
    6. import org.apache.hc.client5.http.classic.ExecChainHandler;
    7. import org.apache.hc.client5.http.classic.methods.HttpGet;
    8. import org.apache.hc.client5.http.impl.ChainElement;
    9. import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
    10. import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
    11. import org.apache.hc.client5.http.impl.classic.HttpClients;
    12. import org.apache.hc.core5.http.ClassicHttpRequest;
    13. import org.apache.hc.core5.http.ClassicHttpResponse;
    14. import org.apache.hc.core5.http.ContentType;
    15. import org.apache.hc.core5.http.EntityDetails;
    16. import org.apache.hc.core5.http.Header;
    17. import org.apache.hc.core5.http.HttpEntity;
    18. import org.apache.hc.core5.http.HttpException;
    19. import org.apache.hc.core5.http.HttpRequest;
    20. import org.apache.hc.core5.http.HttpRequestInterceptor;
    21. import org.apache.hc.core5.http.HttpStatus;
    22. import org.apache.hc.core5.http.io.entity.EntityUtils;
    23. import org.apache.hc.core5.http.io.entity.StringEntity;
    24. import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
    25. import org.apache.hc.core5.http.protocol.HttpContext;
    26. /**
    27.  * 展示如何在请求和响应时进行拦截进行自定义处理。
    28.  */
    29. public class HttpClient5Interceptors {
    30.     public static void main(final String[] args) throws Exception {
    31.         try (final CloseableHttpClient httpclient = HttpClients.custom()
    32.             // 添加一个请求 id 到请求 header
    33.             .addRequestInterceptorFirst(new HttpRequestInterceptor() {
    34.                 private final AtomicLong count = new AtomicLong(0);
    35.                 @Override
    36.                 public void process(
    37.                     final HttpRequest request,
    38.                     final EntityDetails entity,
    39.                     final HttpContext context) throws HttpException, IOException {
    40.                     request.setHeader("request-id", Long.toString(count.incrementAndGet()));
    41.                 }
    42.             })
    43.             .addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "custom", new ExecChainHandler() {
    44.                 // 请求 id 为 2 的,模拟 404 响应,并自定义响应的内容。
    45.                 @Override
    46.                 public ClassicHttpResponse execute(
    47.                     final ClassicHttpRequest request,
    48.                     final Scope scope,
    49.                     final ExecChain chain) throws IOException, HttpException {
    50.                     final Header idHeader = request.getFirstHeader("request-id");
    51.                     if (idHeader != null && "2".equalsIgnoreCase(idHeader.getValue())) {
    52.                         final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND,
    53.                             "Oppsie");
    54.                         response.setEntity(new StringEntity("bad luck", ContentType.TEXT_PLAIN));
    55.                         return response;
    56.                     } else {
    57.                         return chain.proceed(request, scope);
    58.                     }
    59.                 }
    60.             })
    61.             .build()) {
    62.             for (int i = 0; i < 3; i++) {
    63.                 final HttpGet httpget = new HttpGet("http://httpbin.org/get");
    64.                 try (final CloseableHttpResponse response = httpclient.execute(httpget)) {
    65.                     System.out.println("----------------------------------------");
    66.                     System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri());
    67.                     System.out.println(response.getCode() + " " + response.getReasonPhrase());
    68.                     System.out.println(EntityUtils.toString(response.getEntity()));
    69.                 }
    70.             }
    71.         }
    72.     }
    73. }

    输出结果。

    1. ----------------------------------------
    2. 执行请求 GET http://httpbin.org/get
    3. 200 OK
    4. {
    5.   "args": {}, 
    6.   "headers": {
    7.     "Accept-Encoding""gzip, x-gzip, deflate"
    8.     "Host""httpbin.org"
    9.     "Request-Id""1"
    10.     "User-Agent""Apache-HttpClient/5.1.3 (Java/1.8.0_151)"
    11.     "X-Amzn-Trace-Id""Root=1-62f615ba-658ccd42182d22534dbba82c"
    12.   }, 
    13.   "origin""42.120.75.221"
    14.   "url""http://httpbin.org/get"
    15. }
    16. ----------------------------------------
    17. 执行请求 GET http://httpbin.org/get
    18. 404 Oppsie
    19. bad luck
    20. ----------------------------------------
    21. 执行请求 GET http://httpbin.org/get
    22. 200 OK
    23. {
    24.   "args": {}, 
    25.   "headers": {
    26.     "Accept-Encoding""gzip, x-gzip, deflate"
    27.     "Host""httpbin.org"
    28.     "Request-Id""3"
    29.     "User-Agent""Apache-HttpClient/5.1.3 (Java/1.8.0_151)"
    30.     "X-Amzn-Trace-Id""Root=1-62f615bb-4eb6ba10736ace0e21d0cb8c"
    31.   }, 
    32.   "origin""42.120.75.221"
    33.   "url""http://httpbin.org/get"
    34. }

  • 相关阅读:
    java+jsp+servlet+sqlserver图书查询系统
    java多线程之概念和3种创建方式(详解)
    springcloud程序启动后,nacos服务中心的服务名称与程序spring.application.name所配置的应用名不一致
    LotusScript中的命名文档
    如何提高面试成功率
    ESP32 IDF开发 应用篇⑳ WebSocket
    mysql面试题7:MySQL事务原理是什么?MySQL事务的隔离级别有哪些?
    【vue+nestjs】qq第三方授权登录【超详细】
    maven的安装即案例
    3D+AR技术的应用,让时尚行业玩出新花样!
  • 原文地址:https://blog.csdn.net/hebiwen95/article/details/126405108