本想封装一下HttpClient5,基本的get请求已经实现,但是Post带参提交一直无法获得参数,代码记录一下,后面再找寻解决办法吧
pom.xml配置,httpclient版本为
5.1.3
org.apache.httpcomponents.client5
httpclient5
${httpclient.version}
封装配置类HttpConnectorConfig,代码如下:
package com.vtarj.pythagoras.explorer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.util.TimeValue;
import org.apache.tomcat.util.json.JSONParser;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Author Vtarj
* @Description TODO
* @Time 2022/3/30 11:03
**/
public class HttpConfig {
//定义HttpClient构造器
private HttpClientBuilder builder = HttpClientBuilder.create();
//定义头信息
private Map headerMap;
//定义CookieStore对象
private CookieStore cookieStore;
//定义Basic Auth管理对象
private BasicCredentialsProvider basicCredentialsProvider;
//定义请求参数
private List pairs;
//定义默认请求类型
private static String contentType = "application/json";
//定义请求和响应字符编码
private Charset reqCode = StandardCharsets.UTF_8;
private Charset resCode = StandardCharsets.UTF_8;
public HttpConfig(){
//设置连接基本配置
//注册访问协议相关的Socket工厂
Registry registry = RegistryBuilder.create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https",trustHttpsCertificates())
.build();
//设置连接池管理器
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
manager.setMaxTotal(100);
manager.setDefaultMaxPerRoute(manager.getMaxTotal() / 2);
manager.setValidateAfterInactivity(TimeValue.ofMinutes(5));
manager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(30,TimeUnit.SECONDS).setTcpNoDelay(true).build());
//为HttpClientBuilder设置连接池
builder.setConnectionManager(manager);
//设置定期清理连接池中过期的连接
builder.evictExpiredConnections();
builder.evictIdleConnections(TimeValue.ofMinutes(3));
//设置请求基础配置
//创建默认CookieStore
cookieStore = new BasicCookieStore();
//创建默认Basic Auth对象
basicCredentialsProvider = new BasicCredentialsProvider();
//设置Http请求基本参数
RequestConfig requestConfig = RequestConfig.custom()
// 设置启用重定向
.setRedirectsEnabled(true)
// 设置最大重定向次数
.setMaxRedirects(30)
// 设置连接超时时间
.setConnectTimeout(2,TimeUnit.MINUTES)
// 设置请求超时时间
.setConnectionRequestTimeout(2,TimeUnit.MINUTES)
// 设置响应超时时间
.setResponseTimeout(2,TimeUnit.MINUTES)
.build();
//为HttpClientBuilder设置连接配置
builder.setDefaultRequestConfig(requestConfig);
//为HttpClientBuilder设置头信息
builder.setDefaultHeaders(buildHeader());
builder.setDefaultCookieStore(cookieStore);
builder.setDefaultCredentialsProvider(basicCredentialsProvider);
}
/**
* 设置默认请求头
*/
private void setDefaultHeader(){
if (this.getHeaderMap() == null){
headerMap = new HashMap<>();
}
if(!headerMap.containsKey("User-Agent")){
headerMap.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55");
}
if (!headerMap.containsKey("Content-Type")){
headerMap.put("Content-Type", contentType);
}
}
/**
* 将Map类型的Header构建为标准Header列表
* @return
*/
private List buildHeader(){
setDefaultHeader();
return this.getHeaderMap()
.entrySet().stream()
.map(v -> new BasicHeader(v.getKey(),v.getValue()))
.collect(Collectors.toList());
}
/**
* 将参数列表转换为字符串拼接,以&符分隔
* @param list 参数列表
* @return 转换后的字符串
*/
public static String pairsToString(List list){
return list.stream().map(v -> v.getName() + "=" + v.getValue()).collect(Collectors.joining("&"));
}
/**
* 构建Get请求
* @param uri 请求地址
*/
public HttpGet buildGet(String uri){
uri = formatURI(uri);
if (this.getPairs() != null && this.getPairs().size() > 0){
uri = uri.contains("?") ? uri + "&" : uri + "?";
uri = uri + pairsToString(this.getPairs());
}
HttpGet httpGet = new HttpGet(uri);
/**
List headerList = this.buildHeader();
Header[] headers = headerList.toArray(new Header[headerList.size()]);
httpGet.setHeaders(headers);
**/
return httpGet;
}
/**
* 构建Post请求,模拟Form表单提交
* @param uri 请求地址
* @return Post请求
*/
public HttpPost buildPost(String uri){
uri = formatURI(uri);
HttpPost httpPost = new HttpPost(uri);
httpPost.addHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");
//设置请求参数
if (this.getPairs() != null && this.getPairs().size() > 0){
httpPost.setEntity(new UrlEncodedFormEntity(pairs,this.getReqCode()));
}
return httpPost;
}
/**
* 格式化uri连接地址,补全协议
* @param uri 待格式化的连接地址
* @return 格式化后的地址
*/
private static String formatURI(String uri) {
if (!uri.toLowerCase().startsWith("http://") && !uri.toLowerCase().startsWith("https://")){
return "http://" + uri;
}
return uri;
}
/**
* Https证书管理
* @return 可识别证书集合
*/
private static ConnectionSocketFactory trustHttpsCertificates() {
TrustManager[] trustAllCertificates = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null,trustAllCertificates,new SecureRandom());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
}
public HttpClientBuilder getBuilder() {
return builder;
}
public Map getHeaderMap() {
return headerMap;
}
/**
* 设置请求头信息
* @param headerMap 请求头信息
* @return 返回HttpConfig,用于链式调用
*/
public HttpConfig setHeaderMap(Map headerMap) {
this.headerMap = headerMap;
return this;
}
/**
* 自定义添加请求头信息
* @param key 请求头键
* @param value 请求头值
* @return 返回HttpConfig,用于链式调用
*/
public HttpConfig addHeader(String key,String value){
this.headerMap.put(key,value);
return this;
}
public CookieStore getCookieStore() {
return cookieStore;
}
/**
* 设置CookieStore
* @param cookieStore cookiestore
* @return 返回HttpConfig,用于链式调用
*/
public HttpConfig setCookieStore(CookieStore cookieStore) {
this.cookieStore = cookieStore;
return this;
}
public BasicCredentialsProvider getBasicCredentialsProvider() {
return basicCredentialsProvider;
}
/**
*
* @param basicCredentialsProvider
* @return 返回HttpConfig,用于链式调用
*/
public HttpConfig setBasicCredentialsProvider(BasicCredentialsProvider basicCredentialsProvider) {
this.basicCredentialsProvider = basicCredentialsProvider;
return this;
}
public List getPairs() {
return pairs;
}
/**
* 设置请求参数
* @param pairs 请求参数集合
* @return 返回HttpConfig,用于链式调用
*/
public HttpConfig setPairs(List pairs) {
this.pairs = pairs;
return this;
}
public Charset getReqCode() {
return reqCode;
}
public HttpConfig setReqCode(Charset reqCode) {
this.reqCode = reqCode;
return this;
}
public Charset getResCode() {
return resCode;
}
public HttpConfig setResCode(Charset resCode) {
this.resCode = resCode;
return this;
}
public HttpExplorer build(){
return new HttpExplorer(this);
}
}
封装探测器类HttpExplorer,代码如下:
package com.vtarj.pythagoras.explorer;
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.protocol.HttpClientContext;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/**
* @Author Vtarj
* @Description TODO
* @Time 2022/3/30 11:00
**/
public class HttpExplorer {
private CloseableHttpClient httpClient = null;
private HttpConfig config;
public HttpExplorer (HttpConfig config) {
if (httpClient == null){
synchronized (HttpExplorer.class) {
this.config = config;
httpClient = config.getBuilder().build();
}
}
}
/**
* 向指定地址发送Get请求
* @param uri 目标地址
* @return 响应信息
*/
public HttpResponse get(String uri){
try {
CloseableHttpResponse response = this.httpClient.execute(config.buildGet(uri));
return HttpResponseHandler.ofString(config.getResCode()).handler(response,this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 向指定地址发送Get请求
* @param uri 目标地址
* @return 响应信息
*/
public HttpResponse getFile(String uri, String path){
try {
CloseableHttpResponse response = this.httpClient.execute(config.buildGet(uri));
return HttpResponseHandler
.ofFile(Path.of(path), StandardCopyOption.REPLACE_EXISTING)
.handler(response,this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 向指定地址发送Post请求
* @param uri 目标地址
* @return 响应信息
*/
public HttpResponse post(String uri){
try {
HttpPost httpPost = config.buildPost(uri);
CloseableHttpResponse response = this.httpClient.execute(httpPost,new HttpClientContext());
return HttpResponseHandler.ofString(config.getResCode()).handler(response,this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 创建按构造器,用于构造HttpClient配置
* @return
*/
public static HttpConfig builder(){
return new HttpConfig();
}
}
封装响应类HttpResponse,代码如下:
package com.vtarj.pythagoras.explorer;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ProtocolVersion;
import java.util.Locale;
/**
* @Author Vtarj
* @Description 响应信息封装
* @Time 2022/3/30 15:30
**/
public class HttpResponse {
private final int code;
private final HttpEntity entity;
private final T data;
private final ProtocolVersion version;
private final Locale locale;
private final String reasonPhrase;
public HttpResponse(int code, HttpEntity entity, T data, ProtocolVersion version, Locale locale, String reasonPhrase) {
this.code = code;
this.entity = entity;
this.data = data;
this.version = version;
this.locale = locale;
this.reasonPhrase = reasonPhrase;
}
public static HttpResponse build(ClassicHttpResponse response, T data) {
return new HttpResponse(response.getCode(), response.getEntity(), data, response.getVersion(), response.getLocale(), response.getReasonPhrase());
}
public int getCode() {
return code;
}
public HttpEntity getEntity() {
return entity;
}
public T getData() {
return data;
}
public ProtocolVersion getVersion() {
return version;
}
public Locale getLocale() {
return locale;
}
public String getReasonPhrase() {
return reasonPhrase;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Response{");
sb.append("code=").append(code);
sb.append(", entity=").append(entity);
sb.append(", data=").append(data);
sb.append(", version=").append(version);
sb.append(", locale=").append(locale);
sb.append(", reasonPhrase='").append(reasonPhrase).append('\'');
sb.append('}');
return sb.toString();
}
}
响应代理接口HttpResponseHandler,代码如下:
package com.vtarj.pythagoras.explorer;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* @Author Vtarj
* @Description 响应信息处理
* @Time 2022/3/30 15:31
**/
public interface HttpResponseHandler {
HttpResponse handler(ClassicHttpResponse response, HttpExplorer explorer);
/**
* 返回字符串
*
* @param defaultCharset 默认字符集
*/
static HttpResponseHandler ofString(Charset... defaultCharset) {
return (response, client) -> {
try {
Charset charset = defaultCharset != null && defaultCharset.length > 0 ? defaultCharset[0] : Charset.defaultCharset();
return HttpResponse.build(response, EntityUtils.toString(response.getEntity(), charset));
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
/**
* 保存为文件
*
* @param path 保存文件路径
* @param options StandardCopyOption: REPLACE_EXISTING(替换更新), COPY_ATTRIBUTES(复制属性), ATOMIC_MOVE(原子移动)
*/
static HttpResponseHandler ofFile(Path path, CopyOption... options) {
return (response, client) -> {
File file = path.toFile();
if (!file.exists()) {
file.getParentFile().mkdirs();
}
try (InputStream in = response.getEntity().getContent()) {
Files.copy(in, path, options);
} catch (Exception e) {
throw new RuntimeException(e);
}
return HttpResponse.build(response, file);
};
}
}
程序测试
@Test
void testHttpExplorer4Get(){
//网页资源访问
HttpResponse stringHttpResponse = HttpExplorer.builder()
.build()
.get("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/index.html");
System.out.println(stringHttpResponse.toString());
}
@Test
void testHttpExplorer4GetFile(){
//文件下载
HttpResponse fileHttpResponse = HttpExplorer.builder()
.build()
.getFile("http://**********/YZSoft/Attachment/default.ashx?202203280003","D://a.pdf");
System.out.println(fileHttpResponse.getData().getName());
}
/**
* HttpClient5 的post带参提交无法获得参数,测试失败
*/
@Test
void testPost(){
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost("https://register-api.did.id/v1/account/search");
List nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("account","mama.bit"));
nvps.add(new BasicNameValuePair("address",""));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponse response = httpclient.execute(httpPost);
HttpResponse result = HttpResponseHandler.ofString().handler(response,null);
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* HttpClient5 的post带参提交无法获得参数,测试失败
*/
@Test
void testHttpExplorer4PostWithDAS(){
List nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("account","mama.bit"));
nvps.add(new BasicNameValuePair("address",""));
HttpResponse response = HttpExplorer
.builder()
.setPairs(nvps)
.addHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8")
.build()
.post("https://register-api.did.id/v1/account/search");
System.out.println(response.toString());
}
/**
* HttpClient5 的post带参提交无法获得参数,测试失败
*/
@Test
void testHttpExplorer4Post(){
List nvps = new ArrayList<>();
nvps.add(new BasicNameValuePair("username","sysadmin"));
nvps.add(new BasicNameValuePair("password","Test000000@"));
nvps.add(new BasicNameValuePair("execution","e1s1"));
nvps.add(new BasicNameValuePair("_eventId","submit"));
nvps.add(new BasicNameValuePair("submit", "登录"));
HttpResponse response = HttpExplorer.builder()
.addHeader("Content-Type","text/html;charset=UTF-8")
.addHeader("Referer","http://192.168.1.191:8008/cas/login")
.setPairs(nvps)
.build()
.post("http://192.168.1.191:8008/cas/login");
System.out.println(response.toString());
}
基本封装已经完成,但是存在两个疑问:
疑问一:HttpClientBuilder为何要提供setDefaultHeaders方法?
HttpClientBuilder用于构造HttpClient通道,但是在这里设置Header的目的是什么?后续构造HttpRequest之后仍要重新设置Header,因此不明白这里为何要设置Header等信息?
疑问二:HttpPost.setEntity在网上查找了解到它是以application/x-www-form-urlencoded这种方式模拟Form提交,因此实际无需再指定request的header,但是为何form表单提交后服务端还是无法获取参数?
至此,封装暂告一段落,希望对你有一点微薄帮助