java代理http請求
前言
估計看到這個標(biāo)題你會覺得多此一舉,因為nginx完全可以實現(xiàn)代理,且配置非常方便。之前接到一個需求,服務(wù)器必須使用java程序進行代理,并記錄詳細(xì)的出入?yún)ⅲ员愫罄m(xù)根據(jù)日志查到問題。先暫且不糾結(jié)是否必要,來談?wù)劸唧w實現(xiàn)。
需求明細(xì)
1、使用http代理post接口請求(接口前綴一樣)
2、能接收json或xml數(shù)據(jù),返pdf流文件或json數(shù)據(jù)
3、詳細(xì)記錄出入?yún)?/p>
具體實現(xiàn)
俗話說無圖無真相,慣例邊上代碼邊解釋
1、構(gòu)建springboot項目,添加pom.xml依賴
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>cn.gudukegroupId>
<artifactId>guduke-agentartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>guduke-agent/name>
<description>代理服務(wù)description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.10version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpmimeartifactId>
<version>4.5.10version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-to-slf4jartifactId>
<version>2.10.0version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jul-to-slf4jartifactId>
<version>1.7.25version>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
dependencies>
2、application.yml添加配置信息
server:
servlet:
context-path: /guduke-agent
port: 9530
# 需要代理的接口地址
agent:
insur: http://www.guduke.cn:20001/agent
3、定義工具類AgentUtil
/**
* @ClassName AgentUtil
* @Description 代理工具類
* @Author yuxk
* @Date 2021/10/8 20:15
* @Version 1.0
**/
public class AgentUtil {
public static final String CONTENT_LENGTH = "content-length";
public static final String CONTENT_TYPE = "application/pdf";
private static final Logger logger = LoggerFactory.getLogger(AgentUtil.class);
private static PoolingHttpClientConnectionManager connMgr;
private static RequestConfig requestConfig;
# 設(shè)置1分鐘超時時間
private static final int MAX_TIMEOUT = 60000;
static {
// 設(shè)置連接池
connMgr = new PoolingHttpClientConnectionManager();
// 設(shè)置連接池大小
connMgr.setMaxTotal(100);
connMgr.setDefaultMaxPerRoute(connMgr.getMaxTotal());
connMgr.setValidateAfterInactivity(1000);
RequestConfig.Builder configBuilder = RequestConfig.custom();
// 設(shè)置連接超時
configBuilder.setConnectTimeout(MAX_TIMEOUT);
// 設(shè)置讀取超時
configBuilder.setSocketTimeout(MAX_TIMEOUT);
// 設(shè)置從連接池獲取連接實例的超時
configBuilder.setConnectionRequestTimeout(MAX_TIMEOUT);
requestConfig = configBuilder.build();
}
/**
* @Author yuxk
* @Description post請求
* @Date 2022/4/15 9:05
* @Param apiUrl 請求地址
* @Param headersMap 請求頭
* @Param json 請求參數(shù)
* @Param infno 功能號
* @Param contentType 請求類型
* @Return java.util.Map
**/
public static Map doPost(String apiUrl, Map headersMap, Object json, String infno, String contentType) {
Map resultMap = new HashMap<>();
CloseableHttpClient httpClient = null;
// 根據(jù)http或https構(gòu)建CloseableHttpClient對象
if (apiUrl.startsWith("https")) {
httpClient = HttpClients.custom().setSSLSocketFactory(createSSLConnSocketFactory())
.setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig).build();
} else {
httpClient = HttpClients.createDefault();
}
Object httpStr = null;
HttpPost httpPost = new HttpPost(apiUrl);
CloseableHttpResponse response = null;
try {
httpPost.setConfig(requestConfig);
// 解決中文亂碼問題
StringEntity stringEntity = new StringEntity(json.toString(), "UTF-8");
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType(contentType);
httpPost.setEntity(stringEntity);
// 設(shè)置請求頭
if (!headersMap.isEmpty()) {
for (Map.Entry map : headersMap.entrySet()) {
// 去除字段長度,提交會報錯
if (!CONTENT_LENGTH.equals(map.getKey())) {
httpPost.setHeader(map.getKey(), map.getValue());
}
}
}
response = httpClient.execute(httpPost);
// 獲取返回的請求頭信息
Map responseMap = new HashMap<>();
Header[] allHeaders = response.getAllHeaders();
for (Header header : allHeaders) {
responseMap.put(header.getName(), header.getValue());
}
HttpEntity entity = response.getEntity();
// 查看是否為流文件
String disposition = responseMap.get("Content-Disposition");
String type = responseMap.get("Content-Type");
if (!StringUtils.isEmpty(disposition) || CONTENT_TYPE.equals(type)) {
resultMap.put("Content-Disposition", disposition);
if (!StringUtils.isEmpty(type)) {
responseMap.put("Content-Type", type);
}
httpStr = EntityUtils.toByteArray(entity);
} else {
httpStr = EntityUtils.toString(entity, "UTF-8");
}
} catch (IOException e) {
logger.error("調(diào)用服務(wù)異常:{}", e.getMessage());
} finally {
if (response != null) {
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
resultMap.put("result", httpStr);
return resultMap;
}
/**
* @Author yuxk
* @Description 創(chuàng)建SSL安全連接
* @Date 2022/4/15 9:05
* @Param
* @Return org.apache.http.conn.ssl.SSLConnectionSocketFactory
**/
private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
SSLConnectionSocketFactory sslsf = null;
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (GeneralSecurityException e) {
logger.error("SSL連接異常:{}", e.getMessage());
}
return sslsf;
}
/**
* @Author yuxk
* @Description 獲取隨機文件名
* @Date 2022/4/15 9:05
* @Param
* @Return java.lang.String
**/
public static String getFilename() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
return sdf.format(new Date()) + (int) (Math.random() * (9000) + 1000);
}
/**
* @Author yuxk
* @Description request請求參數(shù)轉(zhuǎn)成字節(jié)數(shù)組
* @Date 2022/4/15 9:04
* @Param request
* @Return byte[]
**/
public static byte[] getRequestPostBytes(HttpServletRequest request)
throws IOException {
int contentLength = request.getContentLength();
if (contentLength < 0) {
return null;
}
byte[] buffer = new byte[contentLength];
for (int i = 0; i < contentLength; ) {
int readlen = request.getInputStream().read(buffer, i,
contentLength - i);
if (readlen == -1) {
break;
}
i += readlen;
}
return buffer;
}
}
4、創(chuàng)建代理controller類AgentController
/**
* @ClassName AgentController
* @Description 代理控制類
* @Author yuxk
* @Date 2021/10/8 20:08
* @Version 1.0
**/
@RestController
@Slf4j
public class AgentController {
@Value("${agent.insur}")
private String insurUrl;
@PostMapping("/insur/**")
public ResponseEntity insur(HttpServletRequest request) throws IOException {
String path = request.getServletPath();
log.info("開始調(diào)用接口");
// 獲取接口名稱
String infno = path.replace("/insur", "");
String actualno = infno.startsWith("/") ? infno.substring(1) : "";
log.info("接口名稱:{}", actualno);
// 獲取請求入?yún)?/span>
byte[] bytes = AgentUtil.getRequestPostBytes(request);
String param = new String(bytes, "UTF-8");
log.info("請求入?yún)?{}", param);
// 獲取請求頭數(shù)據(jù)
Map headersMap = new HashMap<>(16);
Enumeration headerNames = request.getHeaderNames();
StringBuilder builder = new StringBuilder(16);
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
headersMap.put(name, value);
builder.append(name + "=" + value + ",");
}
log.info("請求頭參數(shù):{}", StringUtils.isEmpty(builder.toString()) ? "" : builder.substring(0, builder.length() - 1));
// 獲取請求方式
String contentType = request.getContentType();
// 調(diào)用接口,獲取結(jié)果集和文件類型
String realUrl= insurUrl + infno;
Map resultMap = AgentUtil.doPost(realUrl, headersMap, param, contentType, actualno);
String disposition = (String) resultMap.get("Content-Disposition");
String type = (String) resultMap.get("Content-Type");
if (!StringUtils.isEmpty(disposition) || AgentUtil.CONTENT_TYPE.equals(type)) {
// 文件以流返回
HttpHeaders headers = new HttpHeaders();
if (!StringUtils.isEmpty(type)) {
headers.add("Content-Type", type);
}
if (AgentUtil.CONTENT_TYPE.equals(type)) {
headers.add("Content-Disposition", AgentUtil.getFilename() + ".pdf");
} else {
headers.add("Content-Disposition", disposition);
}
log.info("請求回參:{}", resultMap.get("result"));
return new ResponseEntity<>(resultMap.get("result"), headers, HttpStatus.OK);
}
log.info("請求回參:{}", resultMap.get("result"));
return new ResponseEntity<>(resultMap.get("result"), HttpStatus.OK);
}
}
@PostMapping("/insur/**")?路徑帶**可以匹配多個路徑request.getServletPath()?獲取請求路徑如/insur/1101,目的是為了后面進行路徑替換request.getHeaderNames()?獲取所有的請求頭參數(shù),便于后面java請求時把原請求頭參數(shù)一并帶過去
獲取請求結(jié)果之后的Content-Disposition和Content-Type數(shù)據(jù),若是流文件java以流返回,若是json數(shù)據(jù)直接返回
5、構(gòu)建切面類ControllerAspect添加日志請求ID
/**
* @ClassName ControllerAspect
* @Description TODO
* @Author yuxk
* @Date 2021/12/15 13:52
* @Version 1.0
**/
@Component
@Aspect
public class ControllerAspect {
@Pointcut("execution(* cn.hsa.powersi.controller.*.*(..))")
public void executionService() {
}
@Before(value = "executionService()")
public void doBefore(JoinPoint joinPoint) {
// 添加日志打印
String requestId = MDC.get("requestId");
if (StringUtils.isEmpty(requestId)) {
requestId = String.valueOf(UUID.randomUUID());
MDC.put("traceID", requestId);
}
}
@AfterReturning(pointcut = "executionService()")
public void doAfter() {
MDC.clear();
}
@AfterThrowing(pointcut = "executionService()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
MDC.clear();
}
}
6、創(chuàng)建logback-spring.xml日志配置文件,保存出入?yún)⒌轿募?/p>
復(fù)制"1.0" encoding="UTF-8"?>
"false">
"context" name="LOG_HOME" source="logging.file.path" defaultValue="/app/log/guduke-agent"/>
"context" name="APP_NAME" source="spring.application.name" defaultValue="guduke-agent"/>
"context" name="ROOT_LEVEL" source="logging.level.root" defaultValue="INFO"/>
"context" name="PATTERN" source="logging.file.pattern"
defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS} - [%X{traceID}] - [%thread] %-5level %logger{50}.%M\(%line\) - %msg%n"/>
"context" name="MAXHISTORY" source="logging.file.maxHistory" defaultValue="180"/>
"context" name="MAXFILESIZE" source="logging.file.maxFileSize" defaultValue="100MB"/>
"context" name="TOTALSIZECAP" source="logging.file.totalSizeCap" defaultValue="10GB"/>
"STDOUT" class="ch.qos.logback.core.ConsoleAppender">
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
${PATTERN}
"FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log
${MAXHISTORY}
${MAXFILESIZE}
${TOTALSIZECAP}
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
${PATTERN}
"prod">
"${ROOT_LEVEL}">
"FILE"/>
"!prod">
"${ROOT_LEVEL}">
"STDOUT"/>
"FILE"/>
評論
圖片
表情
