first commit
This commit is contained in:
@@ -0,0 +1,525 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import com.mgm.mcrm.common.dtos.HttpResult;
|
||||
import com.mgm.mcrm.common.enums.HttpMethods;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPut;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||
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.io.entity.EntityUtils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mgm.mcrm.common.httpclient.HttpEntityBuilder;
|
||||
|
||||
/**
|
||||
* HTTP 客户端执行器(基于 Apache HttpClient 5)
|
||||
*
|
||||
* 这是 HTTP 客户端模块的主要门面类,提供便捷的 HTTP 请求执行方法。
|
||||
* 支持:
|
||||
* - GET、POST、PUT、DELETE 请求
|
||||
* - 自定义请求头和参数
|
||||
* - Cookie 管理
|
||||
* - 详细响应信息获取
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* // 配置请求选项
|
||||
* HttpRequestOptions options = HttpRequestOptions.create()
|
||||
* .url("https://api.example.com/data")
|
||||
* .timeout(requestConfig)
|
||||
* .map(params)
|
||||
* .encoding("utf-8")
|
||||
* .client(httpClient);
|
||||
*
|
||||
* // 配置请求头
|
||||
* Header[] headers = HttpHeaderBuilder.create()
|
||||
* .userAgent("MyApp")
|
||||
* .build();
|
||||
*
|
||||
* // 执行 GET 请求
|
||||
* String result = HttpClientExecutor.get(options, headers);
|
||||
*
|
||||
* // 执行 POST 请求
|
||||
* String result = HttpClientExecutor.post(options, headers);
|
||||
*
|
||||
* // 获取详细响应信息
|
||||
* HttpResult httpResult = HttpClientExecutor.execute(options, headers);
|
||||
* </pre>
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpClientExecutor {
|
||||
|
||||
private HttpClientExecutor() {
|
||||
// 工具类,禁止实例化
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 GET 请求
|
||||
*
|
||||
* @param options HTTP 请求选项
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return 响应体字符串
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static String get(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
return executeGet(options, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 POST 请求
|
||||
*
|
||||
* @param options HTTP 请求选项
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return 响应体字符串
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static String post(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
return executePost(options, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行通用请求(根据 HttpRequestOptions 中的 method 自动选择)
|
||||
*
|
||||
* @param options HTTP 请求选项
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return 响应体字符串
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static String execute(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
return executeRequest(options, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求并返回详细响应信息
|
||||
*
|
||||
* @param options HTTP 请求选项
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static HttpResult executeWithResult(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
return executeRequestWithResult(options, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求并返回详细响应信息
|
||||
*
|
||||
* @param config HTTP 请求配置
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static HttpResult sendAndPostResp(HttpRequestOptions config, Header[] headers) throws Exception {
|
||||
return executePostRes(config, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求并返回详细响应信息
|
||||
*
|
||||
* @param config HTTP 请求配置
|
||||
* @param headers 请求头数组(可选,可为 null)
|
||||
* @return HttpResult 包含完整的响应信息(状态码、响应头、响应体等)
|
||||
* @throws Exception 请求执行异常
|
||||
*/
|
||||
public static HttpResult sendAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
|
||||
return executeAndGetResp(config, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求并返回详细响应信息(内部方法)
|
||||
*/
|
||||
private static HttpResult executeAndGetResp(HttpRequestOptions config, Header[] headers) throws Exception {
|
||||
String url = config.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
// 1. 先解析 URL 中原本就带的查询参数(如果有)
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
|
||||
|
||||
// 2. 再把 HttpConfig.map() 里的参数也加入到 nvps 中
|
||||
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
|
||||
Map<String, Object> map = config.map();
|
||||
if (map != null && !map.isEmpty()) {
|
||||
// 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
|
||||
HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
|
||||
}
|
||||
|
||||
// 3. 如果 nvps 中有参数,手动构建查询字符串
|
||||
// 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
|
||||
// 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
|
||||
if (!nvps.isEmpty()) {
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
for (int i = 0; i < nvps.size(); i++) {
|
||||
NameValuePair nvp = nvps.get(i);
|
||||
if (i > 0) {
|
||||
queryBuilder.append("&");
|
||||
}
|
||||
try {
|
||||
// 只对参数名进行编码
|
||||
String encodedName = encodeQueryParam(nvp.getName(), config.inenc());
|
||||
// 参数值保持原样,不进行编码(与接口测试工具行为一致)
|
||||
// 如果参数值中包含需要编码的字符(如中文、空格等),可以在这里添加编码逻辑
|
||||
String paramValue = nvp.getValue();
|
||||
queryBuilder.append(encodedName).append("=").append(paramValue);
|
||||
} catch (Exception ex) {
|
||||
// 如果编码失败,使用原始值
|
||||
queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
|
||||
}
|
||||
}
|
||||
String query = queryBuilder.toString();
|
||||
if (url.contains("?")) {
|
||||
url = url + "&" + query;
|
||||
} else {
|
||||
url = url + "?" + query;
|
||||
}
|
||||
}
|
||||
System.out.println(url);
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
setHeaders(httpGet, headers);
|
||||
setRequestConfig(httpGet, config);
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
|
||||
CloseableHttpResponse response;
|
||||
if (config.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet, config.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpGet);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return buildHttpResult(response, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 POST 请求(内部方法)
|
||||
*/
|
||||
private static HttpResult executePostRes(HttpRequestOptions config, Header[] headers) throws Exception {
|
||||
String url = config.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, config.inenc());
|
||||
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
setHeaders(httpPost, headers);
|
||||
setRequestConfig(httpPost, config);
|
||||
|
||||
// 设置请求体
|
||||
Map<String, Object> map = config.map();
|
||||
if (map != null && !map.isEmpty()) {
|
||||
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, config.inenc());
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
// 注意:execute 方法在 HttpClient 5 中被标记为 deprecated,但这是目前推荐的方式
|
||||
CloseableHttpResponse response;
|
||||
if (config.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost, config.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) config.client().execute(httpPost);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return buildHttpResult(response, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 GET 请求(内部方法)
|
||||
*/
|
||||
private static String executeGet(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
String url = options.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
// 1. 先解析 URL 中原本就带的查询参数(如果有)
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
|
||||
|
||||
// 2. 再把 HttpRequestOptions.map() 里的参数也加入到 nvps 中
|
||||
// 对于 GET 请求,这些参数应该拼到 URL 上,而不是放到 body 里
|
||||
Map<String, Object> map = options.map();
|
||||
if (map != null && !map.isEmpty()) {
|
||||
// 这里主要是为了复用参数编码逻辑,返回的 entity 我们不用
|
||||
HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
|
||||
}
|
||||
|
||||
// 3. 如果 nvps 中有参数,手动构建查询字符串
|
||||
// 注意:某些服务器(如接口测试工具)可能期望参数值中的特殊字符(如 == 和 ;)不被编码
|
||||
// 因此我们只对参数名进行编码,参数值保持原样或只编码必要的字符
|
||||
if (!nvps.isEmpty()) {
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
for (int i = 0; i < nvps.size(); i++) {
|
||||
NameValuePair nvp = nvps.get(i);
|
||||
if (i > 0) {
|
||||
queryBuilder.append("&");
|
||||
}
|
||||
try {
|
||||
// 只对参数名进行编码
|
||||
String encodedName = encodeQueryParam(nvp.getName(), options.inenc());
|
||||
// 参数值保持原样,不进行编码(与接口测试工具行为一致)
|
||||
String paramValue = nvp.getValue();
|
||||
queryBuilder.append(encodedName).append("=").append(paramValue);
|
||||
} catch (Exception ex) {
|
||||
// 如果编码失败,使用原始值
|
||||
queryBuilder.append(nvp.getName()).append("=").append(nvp.getValue());
|
||||
}
|
||||
}
|
||||
String query = queryBuilder.toString();
|
||||
if (url.contains("?")) {
|
||||
url = url + "&" + query;
|
||||
} else {
|
||||
url = url + "?" + query;
|
||||
}
|
||||
}
|
||||
System.out.println(url);
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
setHeaders(httpGet, headers);
|
||||
setRequestConfig(httpGet, options);
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
CloseableHttpResponse response;
|
||||
if (options.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpGet, options.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpGet);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return extractResponseBody(response, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 POST 请求(内部方法)
|
||||
*/
|
||||
private static String executePost(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
String url = options.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
|
||||
|
||||
HttpPost httpPost = new HttpPost(url);
|
||||
setHeaders(httpPost, headers);
|
||||
setRequestConfig(httpPost, options);
|
||||
|
||||
// 设置请求体
|
||||
Map<String, Object> map = options.map();
|
||||
if (map != null && !map.isEmpty()) {
|
||||
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
CloseableHttpResponse response;
|
||||
if (options.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpPost, options.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(httpPost);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return extractResponseBody(response, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行通用请求(根据 HttpMethods 自动选择)(内部方法)
|
||||
*/
|
||||
private static String executeRequest(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
HttpMethods method = options.method();
|
||||
String url = options.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
|
||||
|
||||
ClassicHttpRequest request = createRequest(method, url);
|
||||
setHeaders(request, headers);
|
||||
setRequestConfig(request, options);
|
||||
|
||||
// 设置请求体(仅 POST 和 PUT)
|
||||
Map<String, Object> map = options.map();
|
||||
if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
|
||||
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
|
||||
if (request instanceof HttpPost) {
|
||||
((HttpPost) request).setEntity(entity);
|
||||
} else if (request instanceof HttpPut) {
|
||||
((HttpPut) request).setEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
CloseableHttpResponse response;
|
||||
if (options.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request, options.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return extractResponseBody(response, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求并返回详细响应信息(内部方法)
|
||||
*/
|
||||
private static HttpResult executeRequestWithResult(HttpRequestOptions options, Header[] headers) throws Exception {
|
||||
HttpMethods method = options.method();
|
||||
String url = options.url();
|
||||
List<NameValuePair> nvps = new ArrayList<>();
|
||||
url = HttpEntityBuilder.checkHasParas(url, nvps, options.inenc());
|
||||
|
||||
ClassicHttpRequest request = createRequest(method, url);
|
||||
setHeaders(request, headers);
|
||||
setRequestConfig(request, options);
|
||||
|
||||
// 设置请求体(仅 POST 和 PUT)
|
||||
Map<String, Object> map = options.map();
|
||||
if (map != null && !map.isEmpty() && (request instanceof HttpPost || request instanceof HttpPut)) {
|
||||
HttpEntity entity = HttpEntityBuilder.map2HttpEntity(nvps, map, options.inenc());
|
||||
if (request instanceof HttpPost) {
|
||||
((HttpPost) request).setEntity(entity);
|
||||
} else if (request instanceof HttpPut) {
|
||||
((HttpPut) request).setEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果配置了 context,使用 context 执行请求以支持 Cookie
|
||||
CloseableHttpResponse response;
|
||||
if (options.context() != null) {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request, options.context());
|
||||
response = tempResponse;
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
CloseableHttpResponse tempResponse = (CloseableHttpResponse) options.client().execute(request);
|
||||
response = tempResponse;
|
||||
}
|
||||
try (response) {
|
||||
return buildHttpResult(response, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 HTTP 请求对象
|
||||
*/
|
||||
private static ClassicHttpRequest createRequest(HttpMethods method, String url) {
|
||||
switch (method) {
|
||||
case GET:
|
||||
return new HttpGet(url);
|
||||
case POST:
|
||||
return new HttpPost(url);
|
||||
case PUT:
|
||||
return new HttpPut(url);
|
||||
case DELETE:
|
||||
return new HttpDelete(url);
|
||||
default:
|
||||
return new HttpGet(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头
|
||||
*/
|
||||
private static void setHeaders(ClassicHttpRequest request, Header[] headers) {
|
||||
if (headers != null) {
|
||||
for (Header header : headers) {
|
||||
request.addHeader(header.getName(), header.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求配置
|
||||
*/
|
||||
private static void setRequestConfig(ClassicHttpRequest request, HttpRequestOptions options) {
|
||||
if (options.requestConfig() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (request instanceof HttpGet) {
|
||||
((HttpGet) request).setConfig(options.requestConfig());
|
||||
} else if (request instanceof HttpPost) {
|
||||
((HttpPost) request).setConfig(options.requestConfig());
|
||||
} else if (request instanceof HttpPut) {
|
||||
((HttpPut) request).setConfig(options.requestConfig());
|
||||
} else if (request instanceof HttpDelete) {
|
||||
((HttpDelete) request).setConfig(options.requestConfig());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取响应体内容
|
||||
*/
|
||||
private static String extractResponseBody(CloseableHttpResponse response, HttpRequestOptions options) throws Exception {
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
String result = EntityUtils.toString(entity, Charset.forName(options.outenc()));
|
||||
return result;
|
||||
} finally {
|
||||
EntityUtils.consume(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 HttpResult 对象
|
||||
*/
|
||||
private static HttpResult buildHttpResult(CloseableHttpResponse response, HttpRequestOptions options) throws Exception {
|
||||
// 直接使用 HttpResult 构造函数,自动设置状态行、响应头、协议版本和状态码
|
||||
HttpResult result = new HttpResult(response);
|
||||
|
||||
// 读取响应体内容
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity != null) {
|
||||
String responseBody = EntityUtils.toString(entity, Charset.forName(options.outenc()));
|
||||
result.setResult(responseBody);
|
||||
EntityUtils.consume(entity);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码查询参数名(只编码参数名,不编码参数值)
|
||||
* 这样可以保持与接口测试工具的行为一致
|
||||
*
|
||||
* @param param 参数名
|
||||
* @param encoding 编码格式
|
||||
* @return 编码后的参数名
|
||||
*/
|
||||
private static String encodeQueryParam(String param, String encoding) {
|
||||
if (param == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
// 只对参数名进行标准 URL 编码
|
||||
return java.net.URLEncoder.encode(param, encoding).replace("+", "%20");
|
||||
} catch (java.io.UnsupportedEncodingException e) {
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import org.apache.hc.client5.http.cookie.BasicCookieStore;
|
||||
import org.apache.hc.client5.http.cookie.CookieStore;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
|
||||
/**
|
||||
* HTTP Cookie 上下文管理类
|
||||
*
|
||||
* 用于设置和携带 Cookie,配合 {@link HttpClientExecutor} 使用。
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpCookieContext {
|
||||
/**
|
||||
* 使用httpcontext,用于设置和携带Cookie
|
||||
*/
|
||||
private HttpClientContext context ;
|
||||
|
||||
/**
|
||||
* 储存Cookie
|
||||
*/
|
||||
private CookieStore cookieStore;
|
||||
|
||||
public static HttpCookieContext create(){
|
||||
return new HttpCookieContext();
|
||||
}
|
||||
|
||||
private HttpCookieContext(){
|
||||
this.context = new HttpClientContext();
|
||||
this.cookieStore = new BasicCookieStore();
|
||||
this.context.setCookieStore(cookieStore);
|
||||
}
|
||||
|
||||
public HttpClientContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public HttpCookieContext setContext(HttpClientContext context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CookieStore getCookieStore() {
|
||||
return cookieStore;
|
||||
}
|
||||
|
||||
public HttpCookieContext setCookieStore(CookieStore cookieStore) {
|
||||
this.cookieStore = cookieStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
|
||||
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
|
||||
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.NameValuePair;
|
||||
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
|
||||
import org.apache.hc.core5.http.io.entity.FileEntity;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicNameValuePair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* HTTP 实体构建器
|
||||
*
|
||||
* 用于构建 HTTP 请求实体(HttpEntity),支持:
|
||||
* - URL 编码表单(form-urlencoded)
|
||||
* - JSON 实体
|
||||
* - 文件上传(multipart/form-data)
|
||||
* - 字节数组实体
|
||||
* - 文件实体
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpEntityBuilder {
|
||||
|
||||
//传入参数特定类型
|
||||
public static final String ENTITY_STRING="$ENTITY_STRING$";
|
||||
public static final String ENTITY_JSON="$ENTITY_JSON$";
|
||||
public static final String ENTITY_FILE="$ENTITY_FILEE$";
|
||||
public static final String ENTITY_BYTES="$ENTITY_BYTES$";
|
||||
public static final String ENTITY_INPUTSTREAM="$ENTITY_INPUTSTREAM$";
|
||||
public static final String ENTITY_SERIALIZABLE="$ENTITY_SERIALIZABLE$";
|
||||
public static final String ENTITY_MULTIPART="$ENTITY_MULTIPART$";
|
||||
private static final List<String> SPECIAL_ENTITIY = Arrays.asList(ENTITY_STRING, ENTITY_JSON, ENTITY_BYTES, ENTITY_FILE, ENTITY_INPUTSTREAM, ENTITY_SERIALIZABLE, ENTITY_MULTIPART);
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HttpEntityBuilder.class);
|
||||
|
||||
/**
|
||||
* 检测url是否含有参数,如果有,则把参数加到参数列表中
|
||||
*
|
||||
* @param url 资源地址
|
||||
* @param nvps 参数列表
|
||||
* @param encoding 编码
|
||||
* @return 返回去掉参数的url
|
||||
* @throws UnsupportedEncodingException 不支持的编码异常
|
||||
*/
|
||||
public static String checkHasParas(String url, List<NameValuePair> nvps, String encoding) throws UnsupportedEncodingException {
|
||||
// 检测url中是否存在参数
|
||||
if (url.contains("?") && url.indexOf("?") < url.indexOf("=")) {
|
||||
Map<String, Object> map = buildParas(url.substring(url.indexOf("?") + 1));
|
||||
map2HttpEntity(nvps, map, encoding);
|
||||
url = url.substring(0, url.indexOf("?"));
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 参数转换,将map中的参数,转到参数列表中
|
||||
*
|
||||
* @param nvps 参数列表
|
||||
* @param map 参数列表(map)
|
||||
* @param encoding 编码
|
||||
* @return 返回HttpEntity
|
||||
* @throws UnsupportedEncodingException 不支持的编码异常
|
||||
*/
|
||||
public static HttpEntity map2HttpEntity(List<NameValuePair> nvps, Map<String, Object> map, String encoding) throws UnsupportedEncodingException {
|
||||
HttpEntity entity = null;
|
||||
if(map!=null && map.size()>0){
|
||||
boolean isSpecial = false;
|
||||
// 拼接参数
|
||||
for (Entry<String, Object> entry : map.entrySet()) {
|
||||
if(SPECIAL_ENTITIY.contains(entry.getKey())){//判断是否在之中
|
||||
isSpecial = true;
|
||||
if(ENTITY_STRING.equals(entry.getKey())){//string
|
||||
entity = new StringEntity(String.valueOf(entry.getValue()), Charset.forName(encoding));
|
||||
break;
|
||||
}else if(ENTITY_JSON.equals(entry.getKey())){//json
|
||||
ContentType jsonContentType;
|
||||
if (encoding != null && !encoding.trim().isEmpty()) {
|
||||
// 创建带有编码的JSON ContentType
|
||||
jsonContentType = ContentType.APPLICATION_JSON.withCharset(Charset.forName(encoding));
|
||||
}else {
|
||||
// 使用默认的JSON ContentType(通常UTF-8)
|
||||
jsonContentType = ContentType.APPLICATION_JSON;
|
||||
}
|
||||
// 创建实体时直接传入ContentType
|
||||
entity = new StringEntity(String.valueOf(entry.getValue()), jsonContentType);
|
||||
break;
|
||||
}else if(ENTITY_BYTES.equals(entry.getKey())){//file
|
||||
byte[] data = (byte[]) entry.getValue();
|
||||
entity = new ByteArrayEntity(data, ContentType.APPLICATION_OCTET_STREAM);
|
||||
break;
|
||||
}else if(ENTITY_FILE.equals(entry.getKey())){//file
|
||||
if(File.class.isAssignableFrom(entry.getValue().getClass())){
|
||||
entity = new FileEntity((File)entry.getValue(), ContentType.APPLICATION_OCTET_STREAM);
|
||||
}else if(entry.getValue().getClass()==String.class){
|
||||
entity = new FileEntity(new File((String) entry.getValue()), ContentType.create("text/plain", "UTF-8"));
|
||||
}
|
||||
break;
|
||||
}else if(ENTITY_INPUTSTREAM.equals(entry.getKey())){//inputstream
|
||||
// entity = new InputStreamEntity();
|
||||
break;
|
||||
}else if(ENTITY_SERIALIZABLE.equals(entry.getKey())){//serializeable
|
||||
// entity = new SerializableEntity()
|
||||
break;
|
||||
}else if(ENTITY_MULTIPART.equals(entry.getKey())){//MultipartEntityBuilder
|
||||
File[] files = null;
|
||||
if(File.class.isAssignableFrom(entry.getValue().getClass().getComponentType())){
|
||||
files=(File[])entry.getValue();
|
||||
}else if(entry.getValue().getClass().getComponentType()==String.class){
|
||||
String[] names = (String[]) entry.getValue();
|
||||
files = new File[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
files[i] = new File(names[i]);
|
||||
}
|
||||
}
|
||||
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
||||
builder.setCharset(Charset.forName(encoding));// 设置请求的编码格式
|
||||
builder.setMode(HttpMultipartMode.LEGACY);// 设置浏览器兼容模式
|
||||
int count = 0;
|
||||
for (File file : files) {
|
||||
// //把文件转换成流对象FileBody
|
||||
// FileBody fileBody = new FileBody(file);
|
||||
// builder.addPart(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++, fileBody);
|
||||
builder.addBinaryBody(String.valueOf(map.get(ENTITY_MULTIPART+".name")) + count++,file);
|
||||
}
|
||||
boolean forceRemoveContentTypeCharset = (Boolean)map.get(ENTITY_MULTIPART+".rmCharset");
|
||||
Map<String, Object> m = new HashMap<String, Object>();
|
||||
m.putAll(map);
|
||||
m.remove(ENTITY_MULTIPART);
|
||||
m.remove(ENTITY_MULTIPART+".name");
|
||||
m.remove(ENTITY_MULTIPART+".rmCharset");
|
||||
Iterator<Entry<String, Object>> iterator = m.entrySet().iterator();
|
||||
// 发送的数据
|
||||
while (iterator.hasNext()) {
|
||||
Entry<String, Object> e = iterator.next();
|
||||
builder.addTextBody(e.getKey(), String.valueOf(e.getValue()), ContentType.create("text/plain", encoding));
|
||||
}
|
||||
entity = builder.build();// 生成 HTTP POST 实体
|
||||
|
||||
//强制去除contentType中的编码设置,否则,在某些情况下会导致上传失败
|
||||
if(forceRemoveContentTypeCharset){
|
||||
removeContentTypeCharset(encoding, entity);
|
||||
}
|
||||
break;
|
||||
}else {
|
||||
nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
|
||||
}
|
||||
}else{
|
||||
nvps.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
|
||||
}
|
||||
}
|
||||
if(!isSpecial) {
|
||||
entity = new UrlEncodedFormEntity(nvps, Charset.forName(encoding));
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除content-type中的charset
|
||||
*
|
||||
* @param encoding 编码
|
||||
* @param entity 请求参数及数据信息
|
||||
*/
|
||||
private static void removeContentTypeCharset(String encoding, HttpEntity entity) {
|
||||
try {
|
||||
Class<?> clazz = entity.getClass();
|
||||
Field field = clazz.getDeclaredField("contentType");
|
||||
field.setAccessible(true); //将字段的访问权限设为true:即去除private修饰符的影响
|
||||
if(Modifier.isFinal(field.getModifiers())){
|
||||
Field modifiersField = Field.class.getDeclaredField("modifiers"); //去除final修饰符的影响,将字段设为可修改的
|
||||
modifiersField.setAccessible(true);
|
||||
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
}
|
||||
BasicHeader o = (BasicHeader) field.get(entity);
|
||||
field.set(entity, new BasicHeader(HttpHeaders.CONTENT_TYPE, o.getValue().replace("; charset="+encoding,"")));
|
||||
} catch (NoSuchFieldException e) {
|
||||
log.error("Failed to remove content type charset", e);
|
||||
} catch (SecurityException e) {
|
||||
log.error("Failed to remove content type charset", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Failed to remove content type charset", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("Failed to remove content type charset", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成参数
|
||||
* 参数格式:k1=v1&k2=v2
|
||||
*
|
||||
* @param paras 参数列表
|
||||
* @return 返回参数列表(map)
|
||||
*/
|
||||
public static Map<String,Object> buildParas(String paras){
|
||||
String[] p = paras.split("&");
|
||||
String[][] ps = new String[p.length][2];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < p.length; i++) {
|
||||
pos = p[i].indexOf("=");
|
||||
ps[i][0]=p[i].substring(0,pos);
|
||||
ps[i][1]=p[i].substring(pos+1);
|
||||
pos = 0;
|
||||
}
|
||||
return buildParas(ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成参数
|
||||
* 参数类型:{{"k1","v1"},{"k2","v2"}}
|
||||
*
|
||||
* @param paras 参数列表
|
||||
* @return 返回参数列表(map)
|
||||
*/
|
||||
public static Map<String,Object> buildParas(String[][] paras){
|
||||
// 创建参数队列
|
||||
Map<String,Object> map = new HashMap<String, Object>();
|
||||
for (String[] para: paras) {
|
||||
map.put(para[0], para[1]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,698 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.nio.charset.StandardCharsets; // 替代 Consts
|
||||
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
|
||||
|
||||
/**
|
||||
* HTTP 请求头构建器
|
||||
*
|
||||
* 用于构建 HTTP 请求头,支持链式调用。
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpHeaderBuilder {
|
||||
|
||||
private HttpHeaderBuilder() {}
|
||||
|
||||
public static HttpHeaderBuilder create() {
|
||||
return new HttpHeaderBuilder();
|
||||
}
|
||||
|
||||
//记录head头信息
|
||||
private Map<String, Header> headerMaps = new HashMap<String, Header>();
|
||||
|
||||
/**
|
||||
* 自定义header头信息
|
||||
*
|
||||
* @param key header-key
|
||||
* @param value header-value
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder other(String key, String value) {
|
||||
headerMaps.put(key, new BasicHeader(key, value));
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 指定客户端能够接收的内容类型
|
||||
* 例如:Accept: text/plain, text/html
|
||||
*
|
||||
* @param accept accept
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder accept(String accept) {
|
||||
headerMaps.put(HttpReqHead.ACCEPT,
|
||||
new BasicHeader(HttpReqHead.ACCEPT, accept));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器可以接受的字符编码集
|
||||
* 例如:Accept-Charset: iso-8859-5
|
||||
*
|
||||
* @param acceptCharset accept-charset
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder acceptCharset(String acceptCharset) {
|
||||
headerMaps.put(HttpReqHead.ACCEPT_CHARSET,
|
||||
new BasicHeader(HttpReqHead.ACCEPT_CHARSET, acceptCharset));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定浏览器可以支持的web服务器返回内容压缩编码类型
|
||||
* 例如:Accept-Encoding: compress, gzip
|
||||
*
|
||||
* @param acceptEncoding accept-encoding
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder acceptEncoding(String acceptEncoding) {
|
||||
headerMaps.put(HttpReqHead.ACCEPT_ENCODING,
|
||||
new BasicHeader(HttpReqHead.ACCEPT_ENCODING, acceptEncoding));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器可接受的语言
|
||||
* 例如:Accept-Language: en,zh
|
||||
*
|
||||
* @param acceptLanguage accept-language
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder acceptLanguage(String acceptLanguage) {
|
||||
headerMaps.put(HttpReqHead.ACCEPT_LANGUAGE,
|
||||
new BasicHeader(HttpReqHead.ACCEPT_LANGUAGE, acceptLanguage));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可以请求网页实体的一个或者多个子范围字段
|
||||
* 例如:Accept-Ranges: bytes
|
||||
*
|
||||
* @param acceptRanges accept-ranges
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder acceptRanges(String acceptRanges) {
|
||||
headerMaps.put(HttpReqHead.ACCEPT_RANGES,
|
||||
new BasicHeader(HttpReqHead.ACCEPT_RANGES, acceptRanges));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP授权的授权证书
|
||||
* 例如:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
*
|
||||
* @param authorization authorization
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder authorization(String authorization) {
|
||||
headerMaps.put(HttpReqHead.AUTHORIZATION,
|
||||
new BasicHeader(HttpReqHead.AUTHORIZATION, authorization));
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpHeaderBuilder ac_property_id(String ac_property_id) {
|
||||
headerMaps.put(HttpReqHead.AC_PROPERTY_ID,
|
||||
new BasicHeader(HttpReqHead.AC_PROPERTY_ID, ac_property_id));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定请求和响应遵循的缓存机制
|
||||
* 例如:Cache-Control: no-cache
|
||||
*
|
||||
* @param cacheControl cache-control
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder cacheControl(String cacheControl) {
|
||||
headerMaps.put(HttpReqHead.CACHE_CONTROL,
|
||||
new BasicHeader(HttpReqHead.CACHE_CONTROL, cacheControl));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示是否需要持久连接(HTTP 1.1默认进行持久连接)
|
||||
* 例如:Connection: close 短链接; Connection: keep-alive 长连接
|
||||
*
|
||||
* @param connection connection
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder connection(String connection) {
|
||||
headerMaps.put(HttpReqHead.CONNECTION,
|
||||
new BasicHeader(HttpReqHead.CONNECTION, connection));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器
|
||||
* 例如:Cookie: $Version=1; Skin=new;
|
||||
*
|
||||
* @param cookie cookie
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder cookie(String cookie) {
|
||||
headerMaps.put(HttpReqHead.COOKIE,
|
||||
new BasicHeader(HttpReqHead.COOKIE, cookie));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求内容长度
|
||||
* 例如:Content-Length: 348
|
||||
*
|
||||
* @param contentLength content-length
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder contentLength(String contentLength) {
|
||||
headerMaps.put(HttpReqHead.CONTENT_LENGTH,
|
||||
new BasicHeader(HttpReqHead.CONTENT_LENGTH, contentLength));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求的与实体对应的MIME信息
|
||||
* 例如:Content-Type: application/x-www-form-urlencoded
|
||||
*
|
||||
* @param contentType content-type
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder contentType(String contentType) {
|
||||
headerMaps.put(HttpReqHead.CONTENT_TYPE,
|
||||
new BasicHeader(HttpReqHead.CONTENT_TYPE, contentType));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求发送的日期和时间
|
||||
* 例如:Date: Tue, 15 Nov 2010 08:12:31 GMT
|
||||
*
|
||||
* @param date date
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder date(String date) {
|
||||
headerMaps.put(HttpReqHead.DATE,
|
||||
new BasicHeader(HttpReqHead.DATE, date));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求的特定的服务器行为
|
||||
* 例如:Expect: 100-continue
|
||||
*
|
||||
* @param expect expect
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder expect(String expect) {
|
||||
headerMaps.put(HttpReqHead.EXPECT,
|
||||
new BasicHeader(HttpReqHead.EXPECT, expect));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发出请求的用户的Email
|
||||
* 例如:From: user@email.com
|
||||
*
|
||||
* @param from from
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder from(String from) {
|
||||
headerMaps.put(HttpReqHead.FROM,
|
||||
new BasicHeader(HttpReqHead.FROM, from));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定请求的服务器的域名和端口号
|
||||
* 例如:Host: blog.csdn.net
|
||||
*
|
||||
* @param host host
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder host(String host) {
|
||||
headerMaps.put(HttpReqHead.HOST,
|
||||
new BasicHeader(HttpReqHead.HOST, host));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有请求内容与实体相匹配才有效
|
||||
* 例如:If-Match: "737060cd8c284d8af7ad3082f209582d"
|
||||
*
|
||||
* @param ifMatch if-match
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder ifMatch(String ifMatch) {
|
||||
headerMaps.put(HttpReqHead.IF_MATCH,
|
||||
new BasicHeader(HttpReqHead.IF_MATCH, ifMatch));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码
|
||||
* 例如:If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
|
||||
*
|
||||
* @param ifModifiedSince if-modified-Since
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder ifModifiedSince(String ifModifiedSince) {
|
||||
headerMaps.put(HttpReqHead.IF_MODIFIED_SINCE,
|
||||
new BasicHeader(HttpReqHead.IF_MODIFIED_SINCE, ifModifiedSince));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变
|
||||
* 例如:If-None-Match: "737060cd8c284d8af7ad3082f209582d"
|
||||
*
|
||||
* @param ifNoneMatch if-none-match
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder ifNoneMatch(String ifNoneMatch) {
|
||||
headerMaps.put(HttpReqHead.IF_NONE_MATCH,
|
||||
new BasicHeader(HttpReqHead.IF_NONE_MATCH, ifNoneMatch));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag
|
||||
* 例如:If-Range: "737060cd8c284d8af7ad3082f209582d"
|
||||
*
|
||||
* @param ifRange if-range
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder ifRange(String ifRange) {
|
||||
headerMaps.put(HttpReqHead.IF_RANGE,
|
||||
new BasicHeader(HttpReqHead.IF_RANGE, ifRange));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只在实体在指定时间之后未被修改才请求成功
|
||||
* 例如:If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
|
||||
*
|
||||
* @param ifUnmodifiedSince if-unmodified-since
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder ifUnmodifiedSince(String ifUnmodifiedSince) {
|
||||
headerMaps.put(HttpReqHead.IF_UNMODIFIED_SINCE,
|
||||
new BasicHeader(HttpReqHead.IF_UNMODIFIED_SINCE, ifUnmodifiedSince));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制信息通过代理和网关传送的时间
|
||||
* 例如:Max-Forwards: 10
|
||||
*
|
||||
* @param maxForwards max-forwards
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder maxForwards(String maxForwards) {
|
||||
headerMaps.put(HttpReqHead.MAX_FORWARDS,
|
||||
new BasicHeader(HttpReqHead.MAX_FORWARDS, maxForwards));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用来包含实现特定的指令
|
||||
* 例如:Pragma: no-cache
|
||||
*
|
||||
* @param pragma pragma
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder pragma(String pragma) {
|
||||
headerMaps.put(HttpReqHead.PRAGMA,
|
||||
new BasicHeader(HttpReqHead.PRAGMA, pragma));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到代理的授权证书
|
||||
* 例如:Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
*
|
||||
* @param proxyAuthorization proxy-authorization
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder proxyAuthorization(String proxyAuthorization) {
|
||||
headerMaps.put(HttpReqHead.PROXY_AUTHORIZATION,
|
||||
new BasicHeader(HttpReqHead.PROXY_AUTHORIZATION, proxyAuthorization));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只请求实体的一部分,指定范围
|
||||
* 例如:Range: bytes=500-999
|
||||
*
|
||||
* @param range range
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder range(String range) {
|
||||
headerMaps.put(HttpReqHead.RANGE,
|
||||
new BasicHeader(HttpReqHead.RANGE, range));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 先前网页的地址,当前请求网页紧随其后,即来路
|
||||
* 例如:Referer: http://www.zcmhi.com/archives/71.html
|
||||
*
|
||||
* @param referer referer
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder referer(String referer) {
|
||||
headerMaps.put(HttpReqHead.REFERER,
|
||||
new BasicHeader(HttpReqHead.REFERER, referer));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息
|
||||
* 例如:TE: trailers,deflate;q=0.5
|
||||
*
|
||||
* @param te te
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder te(String te) {
|
||||
headerMaps.put(HttpReqHead.TE,
|
||||
new BasicHeader(HttpReqHead.TE, te));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向服务器指定某种传输协议以便服务器进行转换(如果支持)
|
||||
* 例如:Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
|
||||
*
|
||||
* @param upgrade upgrade
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder upgrade(String upgrade) {
|
||||
headerMaps.put(HttpReqHead.UPGRADE,
|
||||
new BasicHeader(HttpReqHead.UPGRADE, upgrade));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* User-Agent的内容包含发出请求的用户信息
|
||||
*
|
||||
* @param userAgent user-agent
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder userAgent(String userAgent) {
|
||||
headerMaps.put(HttpReqHead.USER_AGENT,
|
||||
new BasicHeader(HttpReqHead.USER_AGENT, userAgent));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关于消息实体的警告信息
|
||||
* 例如:Warn: 199 Miscellaneous warning
|
||||
*
|
||||
* @param warning warning
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder warning(String warning) {
|
||||
headerMaps.put(HttpReqHead.WARNING,
|
||||
new BasicHeader(HttpReqHead.WARNING, warning));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知中间网关或代理服务器地址,通信协议
|
||||
* 例如:Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
|
||||
*
|
||||
* @param via via
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpHeaderBuilder via(String via) {
|
||||
headerMaps.put(HttpReqHead.VIA,
|
||||
new BasicHeader(HttpReqHead.VIA, via));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置此HTTP连接的持续时间(超时时间)
|
||||
* 例如:Keep-Alive: 300
|
||||
*
|
||||
* @param keepAlive keep-alive
|
||||
* @return 返回当前对象
|
||||
*
|
||||
*/
|
||||
public HttpHeaderBuilder keepAlive(String keepAlive) {
|
||||
headerMaps.put(HttpReqHead.KEEP_ALIVE,
|
||||
new BasicHeader(HttpReqHead.KEEP_ALIVE, keepAlive));
|
||||
return this;
|
||||
}
|
||||
|
||||
public String accept() {
|
||||
return get(HttpReqHead.ACCEPT);
|
||||
}
|
||||
|
||||
public String acceptCharset() {
|
||||
return get(HttpReqHead.ACCEPT_CHARSET);
|
||||
}
|
||||
|
||||
public String acceptEncoding() {
|
||||
return get(HttpReqHead.ACCEPT_ENCODING);
|
||||
}
|
||||
|
||||
public String acceptLanguage() {
|
||||
return get(HttpReqHead.ACCEPT_LANGUAGE);
|
||||
}
|
||||
|
||||
public String acceptRanges() {
|
||||
return get(HttpReqHead.ACCEPT_RANGES);
|
||||
}
|
||||
|
||||
public String authorization() {
|
||||
return get(HttpReqHead.AUTHORIZATION);
|
||||
}
|
||||
|
||||
public String cacheControl() {
|
||||
return get(HttpReqHead.CACHE_CONTROL);
|
||||
}
|
||||
|
||||
public String connection() {
|
||||
return get(HttpReqHead.CONNECTION);
|
||||
}
|
||||
|
||||
public String cookie() {
|
||||
return get(HttpReqHead.COOKIE);
|
||||
}
|
||||
|
||||
public String contentLength() {
|
||||
return get(HttpReqHead.CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
public String contentType() {
|
||||
return get(HttpReqHead.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public String date() {
|
||||
return get(HttpReqHead.DATE);
|
||||
}
|
||||
|
||||
public String expect() {
|
||||
return get(HttpReqHead.EXPECT);
|
||||
}
|
||||
|
||||
public String from() {
|
||||
return get(HttpReqHead.FROM);
|
||||
}
|
||||
|
||||
public String host() {
|
||||
return get(HttpReqHead.HOST);
|
||||
}
|
||||
|
||||
public String ifMatch() {
|
||||
return get(HttpReqHead.IF_MATCH);
|
||||
}
|
||||
|
||||
public String ifModifiedSince() {
|
||||
return get(HttpReqHead.IF_MODIFIED_SINCE);
|
||||
}
|
||||
|
||||
public String ifNoneMatch() {
|
||||
return get(HttpReqHead.IF_NONE_MATCH);
|
||||
}
|
||||
|
||||
public String ifRange() {
|
||||
return get(HttpReqHead.IF_RANGE);
|
||||
}
|
||||
|
||||
public String ifUnmodifiedSince() {
|
||||
return get(HttpReqHead.IF_UNMODIFIED_SINCE);
|
||||
}
|
||||
|
||||
public String maxForwards() {
|
||||
return get(HttpReqHead.MAX_FORWARDS);
|
||||
}
|
||||
|
||||
public String pragma() {
|
||||
return get(HttpReqHead.PRAGMA);
|
||||
}
|
||||
|
||||
public String proxyAuthorization() {
|
||||
return get(HttpReqHead.PROXY_AUTHORIZATION);
|
||||
}
|
||||
|
||||
public String referer() {
|
||||
return get(HttpReqHead.REFERER);
|
||||
}
|
||||
|
||||
public String te() {
|
||||
return get(HttpReqHead.TE);
|
||||
}
|
||||
|
||||
public String upgrade() {
|
||||
return get(HttpReqHead.UPGRADE);
|
||||
}
|
||||
|
||||
public String userAgent() {
|
||||
return get(HttpReqHead.USER_AGENT);
|
||||
}
|
||||
|
||||
public String via() {
|
||||
return get(HttpReqHead.VIA);
|
||||
}
|
||||
|
||||
public String warning() {
|
||||
return get(HttpReqHead.WARNING);
|
||||
}
|
||||
|
||||
public String keepAlive() {
|
||||
return get(HttpReqHead.KEEP_ALIVE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取head信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String get(String headName) {
|
||||
if (headerMaps.containsKey(headName)) {
|
||||
return headerMaps.get(headName).getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回header头信息
|
||||
*
|
||||
* @return 返回构建的header头信息数组
|
||||
*/
|
||||
public Header[] build() {
|
||||
Header[] headers = new Header[headerMaps.size()];
|
||||
int i = 0;
|
||||
for (Header header : headerMaps.values()) {
|
||||
headers[i] = header;
|
||||
i++;
|
||||
}
|
||||
headerMaps.clear();
|
||||
headerMaps = null;
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http头信息
|
||||
*
|
||||
* @author arron
|
||||
* @date 2015年11月9日 上午11:29:04
|
||||
* @version 1.0
|
||||
*/
|
||||
private static class HttpReqHead {
|
||||
public static final String ACCEPT = "Accept";
|
||||
public static final String ACCEPT_CHARSET = "Accept-Charset";
|
||||
public static final String ACCEPT_ENCODING = "Accept-Encoding";
|
||||
public static final String ACCEPT_LANGUAGE = "Accept-Language";
|
||||
public static final String ACCEPT_RANGES = "Accept-Ranges";
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
public static final String AC_PROPERTY_ID = "AC-Property-Id";
|
||||
public static final String CACHE_CONTROL = "Cache-Control";
|
||||
public static final String CONNECTION = "Connection";
|
||||
public static final String COOKIE = "Cookie";
|
||||
public static final String CONTENT_LENGTH = "Content-Length";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
public static final String DATE= "Date";
|
||||
public static final String EXPECT = "Expect";
|
||||
public static final String FROM = "From";
|
||||
public static final String HOST = "Host";
|
||||
public static final String IF_MATCH = "If-Match ";
|
||||
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String IF_RANGE = "If-Range";
|
||||
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
|
||||
public static final String KEEP_ALIVE = "Keep-Alive";
|
||||
public static final String MAX_FORWARDS = "Max-Forwards";
|
||||
public static final String PRAGMA = "Pragma";
|
||||
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
|
||||
public static final String RANGE = "Range";
|
||||
public static final String REFERER = "Referer";
|
||||
public static final String TE = "TE";
|
||||
public static final String UPGRADE = "Upgrade";
|
||||
public static final String USER_AGENT = "User-Agent";
|
||||
public static final String VIA = "Via";
|
||||
public static final String WARNING = "Warning";
|
||||
}
|
||||
|
||||
/**
|
||||
* 常用头信息配置
|
||||
*
|
||||
* @author arron
|
||||
* @version 1.0
|
||||
*/
|
||||
public static class Headers{
|
||||
public static final String APP_FORM_URLENCODED="application/x-www-form-urlencoded";
|
||||
public static final String TEXT_PLAIN="text/plain";
|
||||
public static final String TEXT_HTML="text/html";
|
||||
public static final String TEXT_XML="text/xml";
|
||||
public static final String TEXT_JSON="text/json";
|
||||
public static final String CONTENT_CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1.name();
|
||||
public static final String CONTENT_CHARSET_UTF8 = StandardCharsets.UTF_8.name();
|
||||
public static final String DEF_PROTOCOL_CHARSET = StandardCharsets.US_ASCII.name();
|
||||
public static final String CONN_CLOSE = "close";
|
||||
public static final String KEEP_ALIVE = "keep-alive";
|
||||
public static final String EXPECT_CONTINUE = "100-continue";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static Map<String, String> getDefaultHeaders(URLConnection connection) {
|
||||
Map<String, String> defaultHeaders = new HashMap<>();
|
||||
defaultHeaders.put("Accept", "*/*");
|
||||
defaultHeaders.put("Connection", "Keep-Alive");
|
||||
defaultHeaders.put("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
defaultHeaders.put("Accept-Charset", "utf-8");
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 設置請求頭到連接對象
|
||||
*
|
||||
* @param conn URL連接對象
|
||||
* @param contentType 內容類型
|
||||
* @param headers HTTP 請求頭 Map(可選,如果為 null 或空則使用默認值)
|
||||
*/
|
||||
public static void setRequestHeaders(URLConnection conn, String contentType, Map<String, String> headers) {
|
||||
Map<String, String> defaultHeaders = getDefaultHeaders(conn);
|
||||
// 如果傳入了自定義 headers,則合併(自定義的優先)
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
defaultHeaders.putAll(headers);
|
||||
}
|
||||
// 設置所有請求頭
|
||||
for (Map.Entry<String, String> entry : defaultHeaders.entrySet()) {
|
||||
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// Content-Type 優先級最高,如果指定了則覆蓋
|
||||
if (contentType != null) {
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* HTTP 参数构建器
|
||||
*
|
||||
* 用于构建 HTTP 请求参数(查询字符串、请求体等)。
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpParameterBuilder {
|
||||
private static final Logger log = LoggerFactory.getLogger(HttpParameterBuilder.class);
|
||||
|
||||
/**
|
||||
* 將參數 Map 轉換為查詢字符串(用於 GET 請求)
|
||||
*
|
||||
* @param params 參數 Map
|
||||
* @return 查詢字符串,格式為 name1=value1&name2=value2
|
||||
*/
|
||||
public static String buildQueryString(Map<String, Object> params) {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return params.entrySet().stream()
|
||||
.filter(entry -> entry.getKey() != null && entry.getValue() != null)
|
||||
.map(entry -> {
|
||||
try {
|
||||
String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
|
||||
String value = URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8);
|
||||
return key + "=" + value;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to encode parameter: {}={}", entry.getKey(), entry.getValue(), e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.joining("&"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 將參數 Map 轉換為請求體字符串
|
||||
*
|
||||
* @param params 參數 Map
|
||||
* @param contentType 內容類型
|
||||
* @return 請求體字符串
|
||||
*/
|
||||
public static String buildRequestBody(Map<String, Object> params, String contentType) {
|
||||
if (params == null || params.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
// 如果是 JSON 格式
|
||||
if (contentType != null && contentType.contains("application/json")) {
|
||||
// 使用 FastJSON 將 Map 轉換為 JSON 字符串
|
||||
return JSON.toJSONString(params);
|
||||
} else {
|
||||
// 默認使用 form-urlencoded 格式
|
||||
return buildQueryString(params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import com.mgm.mcrm.common.enums.HttpMethods;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.entity.mime.Header;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mgm.mcrm.common.httpclient.HttpEntityBuilder;
|
||||
|
||||
/**
|
||||
* HTTP 请求选项类
|
||||
*
|
||||
* 用于构建 HTTP 请求的选项对象,支持链式调用。
|
||||
* 注意:此类不是 Spring 配置类,而是 HTTP 请求参数的构建器。
|
||||
*
|
||||
* @author Aaka
|
||||
*/
|
||||
public class HttpRequestOptions {
|
||||
|
||||
private HttpRequestOptions(){};
|
||||
|
||||
/**
|
||||
* 获取实例
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public static HttpRequestOptions create(){
|
||||
return new HttpRequestOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpClient对象
|
||||
*/
|
||||
private HttpClient client;
|
||||
|
||||
/**
|
||||
* Header头信息
|
||||
*/
|
||||
private Header[] headers;
|
||||
|
||||
/**
|
||||
* 是否返回response的headers
|
||||
*/
|
||||
private boolean isReturnRespHeaders;
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
*/
|
||||
private HttpMethods method=HttpMethods.GET;
|
||||
|
||||
/**
|
||||
* 请求方法名称
|
||||
*/
|
||||
private String methodName;
|
||||
|
||||
/**
|
||||
* 用于cookie操作
|
||||
*/
|
||||
private HttpContext context;
|
||||
|
||||
/**
|
||||
* 以json格式作为输入参数
|
||||
*/
|
||||
private String json;
|
||||
|
||||
/**
|
||||
* 输入输出编码
|
||||
*/
|
||||
private String encoding= Charset.defaultCharset().displayName();
|
||||
|
||||
/**
|
||||
* 输入编码
|
||||
*/
|
||||
private String inenc;
|
||||
|
||||
/**
|
||||
* 输出编码
|
||||
*/
|
||||
private String outenc;
|
||||
|
||||
/**
|
||||
* 设置RequestConfig
|
||||
*/
|
||||
private RequestConfig requestConfig;
|
||||
|
||||
/**
|
||||
* 解决多线程下载时,strean被close的问题
|
||||
*/
|
||||
private static final ThreadLocal<OutputStream> outs = new ThreadLocal<OutputStream>();
|
||||
|
||||
/**
|
||||
* 解决多线程处理时,url被覆盖问题
|
||||
*/
|
||||
private static final ThreadLocal<String> urls = new ThreadLocal<String>();
|
||||
|
||||
/**
|
||||
* 解决多线程处理时,url被覆盖问题
|
||||
*/
|
||||
private static final ThreadLocal<Map<String,Object>> maps = new ThreadLocal<Map<String,Object>>();
|
||||
|
||||
/**
|
||||
* @param client HttpClient对象
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions client(HttpClient client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url 资源url
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions url(String url) {
|
||||
urls.set(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param headers Header头信息
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions headers(Header[] headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header头信息(是否返回response中的headers)
|
||||
*
|
||||
* @param headers Header头信息
|
||||
* @param isReturnRespHeaders 是否返回response中的headers
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions headers(Header[] headers, boolean isReturnRespHeaders) {
|
||||
this.headers = headers;
|
||||
this.isReturnRespHeaders=isReturnRespHeaders;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method 请求方法
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions method(HttpMethods method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param methodName 请求方法
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions methodName(String methodName) {
|
||||
this.methodName = methodName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context cookie操作相关
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions context(HttpContext context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param map 传递参数
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions map(Map<String, Object> map) {
|
||||
Map<String, Object> m = maps.get();
|
||||
if(m == null || map.isEmpty()){
|
||||
m = map;
|
||||
}else {
|
||||
m.putAll(map);
|
||||
}
|
||||
maps.set(m);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param json 以json格式字符串作为参数
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions json(String json) {
|
||||
this.json = json;
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put(HttpEntityBuilder.ENTITY_JSON, json);
|
||||
maps.set(map);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filePaths 待上传文件所在路径
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions files(String[] filePaths) {
|
||||
return files(filePaths, "file");
|
||||
}
|
||||
/**
|
||||
* 上传文件时用到
|
||||
* @param filePaths 待上传文件所在路径
|
||||
* @param inputName 即file input 标签的name值,默认为file
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions files(String[] filePaths, String inputName) {
|
||||
return files(filePaths, inputName, false);
|
||||
}
|
||||
/**
|
||||
* 上传文件时用到
|
||||
* @param filePaths 待上传文件所在路径
|
||||
* @param inputName 即file input 标签的name值,默认为file
|
||||
* @param forceRemoveContentTypeChraset 是否强制一处content-type中设置的编码类型
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset) {
|
||||
Map<String, Object> m = maps.get();
|
||||
if(m == null || m.isEmpty()){
|
||||
m = new HashMap<String, Object>();
|
||||
}
|
||||
m.put(HttpEntityBuilder.ENTITY_MULTIPART, filePaths);
|
||||
m.put(HttpEntityBuilder.ENTITY_MULTIPART+".name", inputName);
|
||||
m.put(HttpEntityBuilder.ENTITY_MULTIPART+".rmCharset", forceRemoveContentTypeChraset);
|
||||
maps.set(m);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encoding 输入输出编码
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions encoding(String encoding) {
|
||||
//设置输入输出
|
||||
inenc(encoding);
|
||||
outenc(encoding);
|
||||
this.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inenc 输入编码
|
||||
*/
|
||||
public void inenc(String inenc) {
|
||||
this.inenc = inenc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param outenc 输出编码
|
||||
*/
|
||||
public void outenc(String outenc) {
|
||||
this.outenc = outenc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param out 输出流对象
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions out(OutputStream out) {
|
||||
outs.set(out);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时时间
|
||||
*
|
||||
* @param timeout 超时时间,单位-毫秒
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions timeout(int timeout){
|
||||
return timeout(timeout, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时时间以及是否允许网页重定向(自动跳转 302)
|
||||
*
|
||||
* @param timeout 超时时间,单位-毫秒
|
||||
* @param redirectEnable 自动跳转
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions timeout(int timeout, boolean redirectEnable){
|
||||
// 配置请求的超时设置
|
||||
// 注意:HttpClient 5 中需要使用 Timeout 对象,而不是直接的 int 值
|
||||
Timeout timeoutValue = Timeout.ofMilliseconds(timeout);
|
||||
RequestConfig config = RequestConfig.custom()
|
||||
.setConnectionRequestTimeout(timeoutValue)
|
||||
.setResponseTimeout(timeoutValue)
|
||||
.setRedirectsEnabled(redirectEnable)
|
||||
.build();
|
||||
return timeout(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代理、超时时间、允许网页重定向等
|
||||
*
|
||||
* @param requestConfig 超时时间,单位-毫秒
|
||||
* @return 返回当前对象
|
||||
*/
|
||||
public HttpRequestOptions timeout(RequestConfig requestConfig){
|
||||
this.requestConfig = requestConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClient client() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Header[] headers() {
|
||||
return headers;
|
||||
}
|
||||
public boolean isReturnRespHeaders() {
|
||||
return isReturnRespHeaders;
|
||||
}
|
||||
|
||||
public String url() {
|
||||
return urls.get();
|
||||
}
|
||||
|
||||
public HttpMethods method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String methodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public HttpContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public Map<String, Object> map() {
|
||||
return maps.get();
|
||||
}
|
||||
|
||||
public String json() {
|
||||
return json;
|
||||
}
|
||||
|
||||
public String encoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public String inenc() {
|
||||
return inenc == null ? encoding : inenc;
|
||||
}
|
||||
|
||||
public String outenc() {
|
||||
return outenc == null ? encoding : outenc;
|
||||
}
|
||||
|
||||
public OutputStream out() {
|
||||
return outs.get();
|
||||
}
|
||||
|
||||
public RequestConfig requestConfig() {
|
||||
return requestConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
package com.mgm.mcrm.common.httpclient;
|
||||
|
||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.hc.core5.ssl.SSLContexts;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
/**
|
||||
* SSL 支持类(基于 HttpClient 5)
|
||||
*
|
||||
* 提供:
|
||||
* - 信任所有证书的 SSLSocketFactory
|
||||
* - 信任所有证书的 SSLConnectionSocketFactory
|
||||
*
|
||||
* 注意:默认实现信任所有证书,仅适用于内部系统或测试环境。
|
||||
*
|
||||
* @author Anka
|
||||
* @version 2.0 (client5)
|
||||
*/
|
||||
public class HttpSslSupport {
|
||||
private static final SSLHandler SIMPLE_VERIFIER = new SSLHandler();
|
||||
|
||||
private static SSLSocketFactory sslFactory;
|
||||
// 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated,
|
||||
// 但这是目前唯一可用的方式,新的 API 尚未稳定
|
||||
@SuppressWarnings("deprecation")
|
||||
private static SSLConnectionSocketFactory sslConnFactory;
|
||||
|
||||
private static final HttpSslSupport INSTANCE = new HttpSslSupport();
|
||||
|
||||
private SSLContext sc;
|
||||
|
||||
public static HttpSslSupport getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static HttpSslSupport create() {
|
||||
return new HttpSslSupport();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写 X509TrustManager 和 HostnameVerifier,信任所有证书与主机名。
|
||||
* 仅推荐在内部环境或调试场景使用。
|
||||
*/
|
||||
private static class SSLHandler implements X509TrustManager, HostnameVerifier {
|
||||
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return new java.security.cert.X509Certificate[]{};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] chain,
|
||||
String authType) throws java.security.cert.CertificateException {
|
||||
// 信任所有服务器证书
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] chain,
|
||||
String authType) throws java.security.cert.CertificateException {
|
||||
// 信任所有客户端证书
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String paramString, SSLSession paramSSLSession) {
|
||||
// 信任所有主机名
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认 HostnameVerifier(信任所有主机名)
|
||||
*/
|
||||
public static HostnameVerifier getVerifier() {
|
||||
return SIMPLE_VERIFIER;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信任所有证书的 {@link SSLSocketFactory}
|
||||
*/
|
||||
public synchronized SSLSocketFactory getSSLSF(SSLProtocolVersion sslpv) {
|
||||
if (sslFactory != null) {
|
||||
return sslFactory;
|
||||
}
|
||||
try {
|
||||
SSLContext context = getSSLContext(sslpv);
|
||||
context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, null);
|
||||
sslFactory = context.getSocketFactory();
|
||||
return sslFactory;
|
||||
} catch (KeyManagementException e) {
|
||||
throw new RuntimeException("初始化 SSLSocketFactory 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信任所有证书的 {@link SSLConnectionSocketFactory}(HttpClient5)
|
||||
*
|
||||
* 注意:SSLConnectionSocketFactory 在 HttpClient 5 中被标记为 deprecated,
|
||||
* 但这是目前唯一可用的方式,新的 API 尚未稳定
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public synchronized SSLConnectionSocketFactory getSSLCONNSF(SSLProtocolVersion sslpv) {
|
||||
if (sslConnFactory != null) {
|
||||
return sslConnFactory;
|
||||
}
|
||||
try {
|
||||
SSLContext context = getSSLContext(sslpv);
|
||||
context.init(null, new TrustManager[]{SIMPLE_VERIFIER}, new java.security.SecureRandom());
|
||||
sslConnFactory = new SSLConnectionSocketFactory(context, SIMPLE_VERIFIER);
|
||||
return sslConnFactory;
|
||||
} catch (KeyManagementException e) {
|
||||
throw new RuntimeException("初始化 SSLConnectionSocketFactory 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义证书(trustStore)初始化 SSLContext
|
||||
*/
|
||||
public HttpSslSupport customSSL(String keyStorePath, String keyStorepass) {
|
||||
FileInputStream instream = null;
|
||||
try {
|
||||
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
instream = new FileInputStream(new File(keyStorePath));
|
||||
trustStore.load(instream, keyStorepass.toCharArray());
|
||||
// 相信自己的 CA 和所有自签名证书
|
||||
this.sc = SSLContexts.custom()
|
||||
.loadTrustMaterial(trustStore, (chain, authType) -> true)
|
||||
.build();
|
||||
return this;
|
||||
} catch (KeyManagementException | KeyStoreException |
|
||||
NoSuchAlgorithmException | CertificateException | IOException e) {
|
||||
throw new RuntimeException("初始化自定义 SSLContext 失败", e);
|
||||
} finally {
|
||||
if (instream != null) {
|
||||
try {
|
||||
instream.close();
|
||||
} catch (IOException ignore) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取(或创建)指定协议版本的 {@link SSLContext}
|
||||
*/
|
||||
public SSLContext getSSLContext(SSLProtocolVersion sslpv) {
|
||||
try {
|
||||
if (this.sc == null) {
|
||||
this.sc = SSLContext.getInstance(sslpv.getName());
|
||||
}
|
||||
return this.sc;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("不支持的 SSL 协议版本: " + sslpv.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的 SSL/TLS 协议版本枚举
|
||||
*/
|
||||
public enum SSLProtocolVersion {
|
||||
SSL("SSL"),
|
||||
SSLv3("SSLv3"),
|
||||
TLSv1("TLSv1"),
|
||||
TLSv1_1("TLSv1.1"),
|
||||
TLSv1_2("TLSv1.2"),
|
||||
TLSv1_3("TLSv1.3");
|
||||
|
||||
private final String name;
|
||||
|
||||
SSLProtocolVersion(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public static SSLProtocolVersion find(String name) {
|
||||
for (SSLProtocolVersion pv : SSLProtocolVersion.values()) {
|
||||
if (pv.getName().equalsIgnoreCase(name)) {
|
||||
return pv;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未支持当前 SSL 版本号:" + name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user