共计 36257 个字符,预计需要花费 91 分钟才能阅读完成。
点赞 再看,能源有限。微信搜「程序猿阿朗」。
本文 Github.com/niumoo/JavaNotes 和 未读代码博客 曾经收录,有很多知识点和系列文章。
超文本传输协定(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 依赖
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5-fluent</artifactId>
<version>5.1.3</version>
</dependency>
HttpClient 5 Gradle 依赖
implementation 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.1.3'
HttpClient 5 GET 申请
package com.wdbyte.httpclient;
import java.io.IOException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5Get {public static void main(String[] args) {String result = get("http://httpbin.org/get");
System.out.println(result);
}
public static String get(String url) {
String resultContent = null;
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
// 获取状态码
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
System.out.println(response.getReasonPhrase()); // OK
HttpEntity entity = response.getEntity();
// 获取响应信息
resultContent = EntityUtils.toString(entity);
}
} catch (IOException | ParseException e) {e.printStackTrace();
}
return resultContent;
}
}
响应信息:
HTTP/1.1
200
OK
{"args": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",
"X-Amzn-Trace-Id": "Root=1-62bb1891-5ab5e5376ed960471bf32f17"
},
"origin": "47.251.4.198",
"url": "http://httpbin.org/get"
}
HttpClient 5 Fluent GET
应用 Apache HttpClient 5 提供的 Fluent API 能够更便捷的发动 GET 申请,然而可操作的中央较少。
依赖:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5-fluent -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5-fluent</artifactId>
<version>5.1.3</version>
</dependency>
示例:
package com.wdbyte.httpclient;
import java.io.IOException;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.client5.http.fluent.Response;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5GetFluent {public static void main(String[] args) {System.out.println(get("http://httpbin.org/get"));
}
public static String get(String url) {
String result = null;
try {Response response = Request.get(url).execute();
result = response.returnContent().asString();
} catch (IOException e) {e.printStackTrace();
}
return result;
}
}
输入信息:
{"args": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",
"X-Amzn-Trace-Id": "Root=1-62bb190e-1ba46a92645843a04c55da32"
},
"origin": "47.251.4.198",
"url": "http://httpbin.org/get"
}
HttpClient5 GET 申请参数
应用 URIBuilder
的 addParameters()
办法来构建 GET 申请的参数。
package com.wdbyte.httpclient;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.net.URIBuilder;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5GetParams {public static void main(String[] args) {String result = get("http://httpbin.org/get");
System.out.println(result);
}
public static String get(String url) {
String resultContent = null;
HttpGet httpGet = new HttpGet(url);
// 表单参数
List<NameValuePair> nvps = new ArrayList<>();
// GET 申请参数
nvps.add(new BasicNameValuePair("username", "wdbyte.com"));
nvps.add(new BasicNameValuePair("password", "secret"));
// 减少到申请 URL 中
try {URI uri = new URIBuilder(new URI(url))
.addParameters(nvps)
.build();
httpGet.setUri(uri);
} catch (URISyntaxException e) {throw new RuntimeException(e);
}
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
// 获取状态码
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
System.out.println(response.getReasonPhrase()); // OK
HttpEntity entity = response.getEntity();
// 获取响应信息
resultContent = EntityUtils.toString(entity);
}
} catch (IOException | ParseException e) {e.printStackTrace();
}
return resultContent;
}
}
输入信息:
{
"args": {
"password": "secret",
"username": "wdbyte.com"
},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",
"X-Amzn-Trace-Id": "Root=1-62ecc660-69d58a226aefb1b6226541ec"
},
"origin": "42.120.75.185",
"url": "http://httpbin.org/get?username=wdbyte.com&password=secret"
}
上面是通过抓包失去的申请响应信息格式:
// 申请信息
GET /get?username=wdbyte.com&password=secret HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
// 响应信息
HTTP/1.1 200 OK
Date: Fri, 05 Aug 2022 07:27:30 GMT
Content-Type: application/json
Content-Length: 405
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {
"password": "secret",
"username": "wdbyte.com"
},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",
"X-Amzn-Trace-Id": "Root=1-62ecc660-69d58a226aefb1b6226541ec"
},
"origin": "42.120.75.185",
"url": "http://httpbin.org/get?username=wdbyte.com&password=secret"
}
HttpClient 5 POST 申请
上面演示发动一个 POST 申请,并携带表单参数。
参数:username=wdbyte.com&password=secret
package com.wdbyte.httpclient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5Post {public static void main(String[] args) {String result = post("http://httpbin.org/post");
System.out.println(result);
}
public static String post(String url) {
String result = null;
HttpPost httpPost = new HttpPost(url);
// 表单参数
List<NameValuePair> nvps = new ArrayList<>();
// POST 申请参数
nvps.add(new BasicNameValuePair("username", "wdbyte.com"));
nvps.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {try (CloseableHttpResponse response = httpclient.execute(httpPost)) {System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
System.out.println(response.getReasonPhrase()); // OK
HttpEntity entity = response.getEntity();
// 获取响应信息
result = EntityUtils.toString(entity);
// 确保流被齐全生产
EntityUtils.consume(entity);
}
} catch (IOException | ParseException e) {e.printStackTrace();
}
return result;
}
}
输入信息:
HTTP/1.1
200
OK
{"args": {},
"data": "","files": {},"form": {"password":"secret","username":"wdbyte.com"},"headers": {"Accept-Encoding":"gzip, x-gzip, deflate","Content-Length":"35","Content-Type":"application/x-www-form-urlencoded; charset=ISO-8859-1","Host":"httpbin.org","User-Agent":"Apache-HttpClient/5.1.3 (Java/17)","X-Amzn-Trace-Id":"Root=1-62bb1ac8-489b2100728c81d70797a482"},"json": null,"origin":"183.128.136.89","url":"http://httpbin.org/post"
}
上面是通过 Wireshark 抓包失去的申请信息:
POST /post HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Content-Length: 35
Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/17)
username=wdbyte.com&password=secret
HttpClient 5 Fluent POST
应用 Apache HttpClient 5 提供的 Fluent API 能够更便捷的发动 POST 申请,然而可操作的中央较少。
一样发送一个简略的表单参数:username=wdbyte.com&password=secret
package com.wdbyte.httpclient;
import java.io.IOException;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.core5.http.message.BasicNameValuePair;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5PostFluent {public static void main(String[] args) {String result = post("http://httpbin.org/post");
System.out.println(result);
}
public static String post(String url) {
String result = null;
Request request = Request.post(url);
// POST 申请参数
request.bodyForm(new BasicNameValuePair("username", "wdbyte.com"),
new BasicNameValuePair("password", "secret"));
try {result = request.execute().returnContent().asString();
} catch (IOException e) {e.printStackTrace();
}
return result;
}
}
输入信息:
{"args": {},
"data": "","files": {},"form": {"password":"secret","username":"wdbyte.com"},"headers": {"Accept-Encoding":"gzip, x-gzip, deflate","Content-Length":"35","Content-Type":"application/x-www-form-urlencoded; charset=ISO-8859-1","Host":"httpbin.org","User-Agent":"Apache-HttpClient/5.1.3 (Java/17)","X-Amzn-Trace-Id":"Root=1-62bb1c8a-7aee8c004f06919f31a2b533"},"json": null,"origin":"183.128.136.89","url":"http://httpbin.org/post"
}
HttpClient5 POST JSON 参数
应用 StringEntity
类存入 JSON 参数。
package com.wdbyte.httpclient;
import java.io.IOException;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5PostWithJson {public static void main(String[] args) {
String json = "{"
+ "\"password\": \"secret\","
+ "\"username\": \"wdbyte.com\""+"}";
String result = post("http://httpbin.org/post", json);
System.out.println(result);
}
public static String post(String url, String jsonBody) {
String result = null;
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
// 获取响应信息
result = EntityUtils.toString(response.getEntity());
}
} catch (IOException | ParseException e) {e.printStackTrace();
}
return result;
}
}
输入信息:
{"args": {},
"data": "{\"password\": \"secret\", \"username\": \"wdbyte.com\"}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Content-Length": "55",
"Content-Type": "text/plain; charset=ISO-8859-1",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",
"X-Amzn-Trace-Id": "Root=1-62bb1dbb-5a963c1d798b06be3ee1a15e"
},
"json": {
"password": "secret",
"username": "wdbyte.com"
},
"origin": "183.128.136.89",
"url": "http://httpbin.org/post"
}
上面是通过 Wireshark 抓包失去的申请响应信息:
// 申请信息
POST /post HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Content-Length: 55
Content-Type: application/json; charset=UTF-8
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/17)
{"password": "secret", "username": "wdbyte.com"}
// 响应信息
HTTP/1.1 200 OK
Date: Tue, 28 Jun 2022 15:30:17 GMT
Content-Type: application/json
Content-Length: 573
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{"args": {},
"data": "{\"password\": \"secret\", \"username\": \"wdbyte.com\"}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Content-Length": "55",
"Content-Type": "application/json; charset=UTF-8",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/17)",
"X-Amzn-Trace-Id": "Root=1-62bb1e89-64db55730a0361c720232ccd"
},
"json": {
"password": "secret",
"username": "wdbyte.com"
},
"origin": "183.128.136.89",
"url": "http://httpbin.org/post"
}
HttpClient 5 设置超时
应用 RequestConfig
对象来配置超时工夫。
package com.wdbyte.httpclient;
import java.io.IOException;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.Timeout;
/**
* @author https://www.wdbyte.com
*/
public class HttpClient5GetWithTimeout {public static void main(String[] args) {String result = get("http://httpbin.org/get");
System.out.println(result);
}
public static String get(String url) {
String resultContent = null;
// 设置超时工夫
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(5000L))
.setConnectionRequestTimeout(Timeout.ofMilliseconds(5000L))
.setResponseTimeout(Timeout.ofMilliseconds(5000L))
.build();
// 申请级别的超时
HttpGet httpGet = new HttpGet(url);
//httpGet.setConfig(config);
//try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
// 客户端级别的超时
try (CloseableHttpClient httpclient = HttpClients.custom().setDefaultRequestConfig(config).build()) {try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
// 获取状态码
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
System.out.println(response.getReasonPhrase()); // OK
HttpEntity entity = response.getEntity();
// 获取响应信息
resultContent = EntityUtils.toString(entity);
}
} catch (IOException | ParseException e) {e.printStackTrace();
}
return resultContent;
}
}
HttpClient 5 异步申请
上面演示三种 HttpClient 5 异步申请形式。
package com.wdbyte.httpclient;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
/**
* HttpClient 5 异步申请
* @author https://www.wdbyte.com
* @date 2022/06/25
*/
public class HttpClient5Async {public static void main(String[] args) {getAsync1("http://httpbin.org/get");
getAsync2("http://httpbin.org/get");
getAsync3("http://httpbin.org/get");
}
/**
* 异步申请
*
* @param url
* @return
*/
public static String getAsync1(String url) {try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
// 开始 http clinet
httpclient.start();
// 执行申请
SimpleHttpRequest request1 = SimpleHttpRequests.get(url);
Future<SimpleHttpResponse> future = httpclient.execute(request1, null);
// 期待直到返回结束
SimpleHttpResponse response1 = future.get();
System.out.println("getAsync1:" + request1.getRequestUri() + "->" + response1.getCode());
} catch (IOException | ExecutionException | InterruptedException e) {throw new RuntimeException(e);
}
return null;
}
/**
* 异步申请,依据响应状况回调
*
* @param url
* @return
*/
public static String getAsync2(String url) {try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
// 开始 http clinet
httpclient.start();
// 依据申请响应状况进行回调操作
CountDownLatch latch = new CountDownLatch(1);
SimpleHttpRequest request = SimpleHttpRequests.get(url);
httpclient.execute(request, new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response2) {latch.countDown();
System.out.println("getAsync2:" + request.getRequestUri() + "->" + response2.getCode());
}
@Override
public void failed(Exception ex) {latch.countDown();
System.out.println("getAsync2:" + request.getRequestUri() + "->" + ex);
}
@Override
public void cancelled() {latch.countDown();
System.out.println("getAsync2:" + request.getRequestUri() + "cancelled");
}
});
latch.await();} catch (IOException | InterruptedException e) {throw new RuntimeException(e);
}
return null;
}
/**
* 异步申请,对响应流做点什么
*
* @param url
* @return
*/
public static String getAsync3(String url) {try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
// 开始 http clinet
httpclient.start();
// 依据申请响应状况进行回调操作
SimpleHttpRequest request = SimpleHttpRequests.get(url);
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestProducer producer = AsyncRequestBuilder.get("http://httpbin.org/get").build();
AbstractCharResponseConsumer<HttpResponse> consumer3 = new AbstractCharResponseConsumer<HttpResponse>() {
HttpResponse response;
@Override
protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {System.out.println("getAsync3: 开始响应....");
this.response = response;
}
@Override
protected int capacityIncrement() {return Integer.MAX_VALUE;}
@Override
protected void data(CharBuffer data, boolean endOfStream) throws IOException {System.out.println("getAsync3: 收到数据....");
// Do something useful
}
@Override
protected HttpResponse buildResult() throws IOException {System.out.println("getAsync3: 接管结束...");
return response;
}
@Override
public void releaseResources() {}
};
httpclient.execute(producer, consumer3, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response) {latch.countDown();
System.out.println("getAsync3:"+request.getRequestUri() + "->" + response.getCode());
}
@Override
public void failed(Exception ex) {latch.countDown();
System.out.println("getAsync3:"+request.getRequestUri() + "->" + ex);
}
@Override
public void cancelled() {latch.countDown();
System.out.println("getAsync3:"+request.getRequestUri() + "cancelled");
}
});
latch.await();} catch (IOException | InterruptedException e) {throw new RuntimeException(e);
}
return null;
}
}
输入后果:
getAsync1:/get->200
getAsync2:/get->200
getAsync3: 开始响应....
getAsync3: 收到数据....
getAsync3: 收到数据....
getAsync3: 收到数据....
getAsync3: 接管结束...
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 代码进行申请测试
package com.wdbyte.httpclient;
import java.util.List;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.Cookie;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.io.entity.EntityUtils;
/**
* 这个例子演示了应用本地 HTTP 上下文填充, 自定义属性
*/
public class HttpClient5WithCookie {public static void main(final String[] args) throws Exception {try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {
// 创立一个本地的 Cookie 存储
final CookieStore cookieStore = new BasicCookieStore();
// BasicClientCookie clientCookie = new BasicClientCookie("name", "www.wdbyte.com");
// clientCookie.setDomain("http://httpbin.org/cookies");
// 过期工夫
// clientCookie.setExpiryDate(new Date());
// 增加到本地 Cookie
// cookieStore.addCookie(clientCookie);
// 创立本地 HTTP 申请上下文 HttpClientContext
final HttpClientContext localContext = HttpClientContext.create();
// 绑定 cookieStore 到 localContext
localContext.setCookieStore(cookieStore);
final HttpGet httpget = new HttpGet("http://httpbin.org/cookies/set/cookieName/www.wdbyte.com");
System.out.println("执行申请" + httpget.getMethod() + " " + httpget.getUri());
// 获取 Coolie 信息
try (final CloseableHttpResponse response = httpclient.execute(httpget, localContext)) {System.out.println("----------------------------------------");
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final List<Cookie> cookies = cookieStore.getCookies();
for (int i = 0; i < cookies.size(); i++) {System.out.println("Local cookie:" + cookies.get(i));
}
EntityUtils.consume(response.getEntity());
}
}
}
}
输入后果:
执行申请 GET http://httpbin.org/cookies/set/cookieName/www.wdbyte.com
----------------------------------------
200 OK
Local cookie: [name: cookieName; value: www.wdbyte.com; domain: httpbin.org; path: /; expiry: null]
HttpClient 5 读取文件内容申请
筹备一个 JSON 内容格局的文件 params.json。
{"name":"www.wdbyte.com"}
读取这个文件作为申请参数发动申请。
package com.wdbyte.httpclient;
import java.io.File;
import java.io.FileInputStream;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.FileEntity;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
/**
* 加载数据流作为 POST 申请参数
*/
public class HttpClient5ChunkEncodedPost {public static void main(final String[] args) throws Exception {
String params = "/Users/darcy/params.json";
try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {final HttpPost httppost = new HttpPost("http://httpbin.org/post");
final InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(params), -1,
ContentType.APPLICATION_JSON);
// 也能够应用 FileEntity 的模式
// FileEntity reqEntity = new FileEntity(new File(params), ContentType.APPLICATION_JSON);
httppost.setEntity(reqEntity);
System.out.println("执行申请" + httppost.getMethod() + " " + httppost.getUri());
try (final CloseableHttpResponse response = httpclient.execute(httppost)) {System.out.println("----------------------------------------");
System.out.println(response.getCode() + " " + response.getReasonPhrase());
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
}
}
输入后果:
执行申请 POST http://httpbin.org/post
----------------------------------------
200 OK
{"args": {},
"data": "{\"name\":\"www.wdbyte.com\"}\n",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Content-Length": "26",
"Content-Type": "application/json; charset=UTF-8",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",
"X-Amzn-Trace-Id": "Root=1-62ee4d95-1f956d4303cea09c52694c86"
},
"json": {"name": "www.wdbyte.com"},
"origin": "42.120.74.238",
"url": "http://httpbin.org/post"
}
HttpClient 5 表单登录
表单登录能够了解为发动一个携带了认证信息的申请,而后失去响应的 Cookie 的过程。当然这里不仅仅实用于表单登录,也能够是简略的发动一个携带了表单信息的申请。
本应该应用 POST 申请发送表单参数测试,然而在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 申请
示例代码:
package com.wdbyte.httpclient;
import java.util.ArrayList;
import java.util.List;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.Cookie;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
/**
* 演示基于表单的登录
*
* @author https://www.wdbyte.com
*/
public class HttpClient5FormLogin {public static void main(final String[] args) throws Exception {final BasicCookieStore cookieStore = new BasicCookieStore();
try (final CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.build()) {
// 本应该应用 POST 申请发送表单参数,然而在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 申请
// HttpPost httpPost = new HttpPost("http://httpbin.org/cookies/set/username/wdbyte.com");
HttpGet httpPost = new HttpGet("http://httpbin.org/cookies/set/username/wdbyte.com");
// POST 表单申请参数
List<NameValuePair> nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("username", "wdbyte.com"));
nvps.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
try (final CloseableHttpResponse response2 = httpclient.execute(httpPost)) {final HttpEntity entity = response2.getEntity();
System.out.println("Login form get:" + response2.getCode() + " " + response2.getReasonPhrase());
System.out.println("以后响应信息"+EntityUtils.toString(entity));;
System.out.println("Post 登录 Cookie:");
final List<Cookie> cookies = cookieStore.getCookies();
if (cookies.isEmpty()) {System.out.println("None");
} else {for (int i = 0; i < cookies.size(); i++) {System.out.println("-" + cookies.get(i));
}
}
}
}
}
}
输入后果:
Login form get: 200 OK
以后响应信息 {
"cookies": {"username": "wdbyte.com"}
}
Post 登录 Cookie:
- [name: username; value: wdbyte.com; domain: httpbin.org; path: /; expiry: null]
HttpClient 5 Basic Authorization
HTTP 根本认证(Basic Authorization)是一种比较简单的认证实现,次要流程如下
- 申请一个须要进行根本认证的 HTTP 接口,然而没有携带认证信息。
- 此时会响应 401 状态码,并在响应 header 中的 WWW-Authenticate 提醒须要进行根本认证。
- 用户把须要提交认证信息进行冒号拼接,而后进行 base64 编码,再在失去的字符串结尾拼接上 Basic 放入申请头 Authorization 中。
- 认证胜利,响应胜利。
你能够通过浏览器关上上面这个 URL 进行根本认证测试。
http://httpbin.org/basic-auth…
在 Apache HttpClient 5 中的实现形式。
package com.wdbyte.httpclient;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
/**
* 一个简略的示例, 它应用 HttpClient 执行 HTTP 申请;
* 一个须要进行用户身份验证的指标站点。*/
public class HttpClient5BasicAuthentication {public static void main(final String[] args) throws Exception {final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope("httpbin.org", 80),
new UsernamePasswordCredentials("admin", "123456".toCharArray()));
try (final CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.build()) {final HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/admin/123456");
System.out.println("执行申请" + httpget.getMethod() + " " + httpget.getUri());
try (final CloseableHttpResponse response = httpclient.execute(httpget)) {System.out.println("----------------------------------------");
System.out.println(response.getCode() + " " + response.getReasonPhrase());
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
}
}
输入后果:
执行申请 GET http://httpbin.org/basic-auth/user/passwd
----------------------------------------
200 OK
{
"authenticated": true,
"user": "user"
}
通过抓包能够看到残缺的 HTTP 申请响应过程。
// 申请
GET /basic-auth/user/passwd HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
// 响应
HTTP/1.1 401 UNAUTHORIZED
Date: Sat, 06 Aug 2022 08:25:33 GMT
Content-Length: 0
Connection: keep-alive
Server: gunicorn/19.9.0
WWW-Authenticate: Basic realm="Fake Realm"
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// 申请
GET /basic-auth/user/passwd HTTP/1.1
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
Authorization: Basic dXNlcjpwYXNzd2Q=
// 响应
HTTP/1.1 200 OK
Date: Sat, 06 Aug 2022 08:25:33 GMT
Content-Type: application/json
Content-Length: 47
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"authenticated": true,
"user": "user"
}
HttpClient 5 Digest Authorization
HTTP Basic Authorization 的毛病不言而喻,明码通过明文传输存在肯定的平安危险,Digest Authorization 认证形式解决了明文传输的问题,这里不过多介绍 Digest 的相干内容,通过一个图简略的示意 Digest 认证形式的流程。
上面是代码演示。
package com.wdbyte.httpclient;
import org.apache.hc.client5.http.auth.AuthExchange;
import org.apache.hc.client5.http.auth.AuthScheme;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.auth.DigestScheme;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.EntityUtils;
/**
*
* HttpClient 如何验证多个申请的示例
* 应用雷同的摘要计划。在初始申请 / 响应替换之后
* 共享雷同执行上下文的所有后续申请都能够重用
* 要向服务器进行身份验证的最初一个摘要 nonce 值。*/
public class HttpClient5PreemptiveDigestAuthentication {public static void main(final String[] args) throws Exception {try (final CloseableHttpClient httpclient = HttpClients.createDefault()) {final HttpHost target = new HttpHost("http", "httpbin.org", 80);
final HttpClientContext localContext = HttpClientContext.create();
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(target),
new UsernamePasswordCredentials("admin", "123456".toCharArray()));
localContext.setCredentialsProvider(credentialsProvider);
final HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/admin/123456");
System.out.println("执行申请" + httpget.getMethod() + " " + httpget.getUri());
for (int i = 0; i < 2; i++) {try (final CloseableHttpResponse response = httpclient.execute(target, httpget, localContext)) {System.out.println("----------------------------------------");
System.out.println(response.getCode() + " " + response.getReasonPhrase());
EntityUtils.consume(response.getEntity());
final AuthExchange authExchange = localContext.getAuthExchange(target);
if (authExchange != null) {final AuthScheme authScheme = authExchange.getAuthScheme();
if (authScheme instanceof DigestScheme) {final DigestScheme digestScheme = (DigestScheme) authScheme;
System.out.println("Nonce:" + digestScheme.getNonce() +
"; count:" + digestScheme.getNounceCount());
}
}
}
}
}
}
}
通过抓包工具能够清晰的看到 2 次申请的流程,在最初一次申请中,间接共享了认证信息,没有再次的从新认证的流程。
// 1. 申请
GET /digest-auth/auth/admin/123456 HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
// 2. 详情,提醒认证,给出参数
HTTP/1.1 401 UNAUTHORIZED
Date: Fri, 12 Aug 2022 07:11:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Server: gunicorn/19.9.0
WWW-Authenticate: Digest realm="me@kennethreitz.com", nonce="8dc5e7974a86a6fcc3cf73230b0c4a93", qop="auth", opaque="64b7f68b386c3acc38131f7472aa2079", algorithm=MD5, stale=FALSE
Set-Cookie: stale_after=never; Path=/
Set-Cookie: fake=fake_value; Path=/
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// 3. 参数 + 明码 加密后再次申请
GET /digest-auth/auth/admin/123456 HTTP/1.1
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
Cookie: fake=fake_value; stale_after=never
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"
// 4. 认证胜利,响应
HTTP/1.1 200 OK
Date: Fri, 12 Aug 2022 07:11:08 GMT
Content-Type: application/json
Content-Length: 48
Connection: keep-alive
Server: gunicorn/19.9.0
Set-Cookie: fake=fake_value; Path=/
Set-Cookie: stale_after=never; Path=/
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"authenticated": true,
"user": "admin"
}
// 5. 再次申请,共享了登录状态。GET /digest-auth/auth/admin/123456 HTTP/1.1
Accept-Encoding: gzip, x-gzip, deflate
Host: httpbin.org
Connection: keep-alive
User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151)
Cookie: fake=fake_value; stale_after=never
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"
// 5. 认证胜利,响应
HTTP/1.1 200 OK
Date: Fri, 12 Aug 2022 07:11:09 GMT
Content-Type: application/json
Content-Length: 48
Connection: keep-alive
Server: gunicorn/19.9.0
Set-Cookie: fake=fake_value; Path=/
Set-Cookie: stale_after=never; Path=/
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"authenticated": true,
"user": "admin"
}
HttpClient 5 拦截器
HttpClient 5 中的拦截器能够对申请过程的各个阶段进行拦挡解决,通过 HttpClientBuilder
中的对于 Interceptor
的办法能够看到能够进行拦挡的节点。
上面编写一个示例,发动三次申请,每次申请都在申请头 herader 中减少一个 request-id
参数,而后对 request-id
值为 2 的申请间接响应 404 完结。
package com.wdbyte.httpclient;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hc.client5.http.classic.ExecChain;
import org.apache.hc.client5.http.classic.ExecChain.Scope;
import org.apache.hc.client5.http.classic.ExecChainHandler;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.ChainElement;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
/**
* 展现如何在申请和响应时进行拦挡进行自定义解决。*/
public class HttpClient5Interceptors {public static void main(final String[] args) throws Exception {try (final CloseableHttpClient httpclient = HttpClients.custom()
// 增加一个申请 id 到申请 header
.addRequestInterceptorFirst(new HttpRequestInterceptor() {private final AtomicLong count = new AtomicLong(0);
@Override
public void process(
final HttpRequest request,
final EntityDetails entity,
final HttpContext context) throws HttpException, IOException {request.setHeader("request-id", Long.toString(count.incrementAndGet()));
}
})
.addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "custom", new ExecChainHandler() {
// 申请 id 为 2 的,模仿 404 响应,并自定义响应的内容。@Override
public ClassicHttpResponse execute(
final ClassicHttpRequest request,
final Scope scope,
final ExecChain chain) throws IOException, HttpException {final Header idHeader = request.getFirstHeader("request-id");
if (idHeader != null && "2".equalsIgnoreCase(idHeader.getValue())) {
final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND,
"Oppsie");
response.setEntity(new StringEntity("bad luck", ContentType.TEXT_PLAIN));
return response;
} else {return chain.proceed(request, scope);
}
}
})
.build()) {for (int i = 0; i < 3; i++) {final HttpGet httpget = new HttpGet("http://httpbin.org/get");
try (final CloseableHttpResponse response = httpclient.execute(httpget)) {System.out.println("----------------------------------------");
System.out.println("执行申请" + httpget.getMethod() + " " + httpget.getUri());
System.out.println(response.getCode() + " " + response.getReasonPhrase());
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
}
}
}
输入后果。
----------------------------------------
执行申请 GET http://httpbin.org/get
200 OK
{"args": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"Request-Id": "1",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",
"X-Amzn-Trace-Id": "Root=1-62f615ba-658ccd42182d22534dbba82c"
},
"origin": "42.120.75.221",
"url": "http://httpbin.org/get"
}
----------------------------------------
执行申请 GET http://httpbin.org/get
404 Oppsie
bad luck
----------------------------------------
执行申请 GET http://httpbin.org/get
200 OK
{"args": {},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"Request-Id": "3",
"User-Agent": "Apache-HttpClient/5.1.3 (Java/1.8.0_151)",
"X-Amzn-Trace-Id": "Root=1-62f615bb-4eb6ba10736ace0e21d0cb8c"
},
"origin": "42.120.75.221",
"url": "http://httpbin.org/get"
}
判若两人,文章代码都寄存在 Github.com/niumoo/javaNotes.
< 完 >
文章继续更新,能够微信搜一搜「程序猿阿朗 」或拜访「 程序猿阿朗博客」第一工夫浏览。本文 Github.com/niumoo/JavaNotes 曾经收录,有很多知识点和系列文章,欢送 Star。