package com.vincent.rsf.httpaudit.web;
|
|
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
|
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
|
import com.vincent.rsf.httpaudit.service.HttpAuditAsyncRecorder;
|
import com.vincent.rsf.httpaudit.support.HttpAuditSupport;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.core.env.Environment;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.util.ContentCachingRequestWrapper;
|
import org.springframework.web.util.ContentCachingResponseWrapper;
|
|
import javax.servlet.FilterChain;
|
import javax.servlet.ServletException;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.nio.charset.Charset;
|
import java.util.Date;
|
|
/**
|
* 缓存请求/响应体并异步写审计表
|
*/
|
@Slf4j
|
@RequiredArgsConstructor
|
public class HttpAuditFilter extends OncePerRequestFilter {
|
|
private final HttpAuditAsyncRecorder recorder;
|
private final HttpAuditProperties props;
|
private final Environment environment;
|
|
@Override
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
return !props.isEnabled() || HttpAuditSupport.shouldExclude(request, props);
|
}
|
|
@Override
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
throws ServletException, IOException {
|
ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(request, props.getMaxRequestCacheBytes());
|
ContentCachingResponseWrapper resWrapper = new ContentCachingResponseWrapper(response);
|
long t0 = System.currentTimeMillis();
|
Exception chainError = null;
|
try {
|
filterChain.doFilter(reqWrapper, resWrapper);
|
} catch (IOException | ServletException e) {
|
chainError = e;
|
throw e;
|
} catch (RuntimeException e) {
|
chainError = e;
|
throw e;
|
} finally {
|
try {
|
record(reqWrapper, resWrapper, t0, chainError);
|
} catch (Throwable ignore) {
|
log.warn("http-audit record 异常已吞掉:{}", ignore.getMessage());
|
}
|
try {
|
resWrapper.copyBodyToResponse();
|
} catch (IOException io) {
|
log.debug("copyBodyToResponse: {}", io.getMessage());
|
}
|
}
|
}
|
|
private void record(ContentCachingRequestWrapper req, ContentCachingResponseWrapper res, long t0, Exception chainError) {
|
Charset charset = HttpAuditSupport.resolveCharset(req);
|
String ctReq = req.getContentType();
|
String reqBody;
|
if (ctReq != null && ctReq.toLowerCase().startsWith("multipart/")) {
|
reqBody = "[multipart omitted]";
|
} else {
|
reqBody = HttpAuditSupport.bytesToString(req.getContentAsByteArray(), charset);
|
}
|
|
String respCt = res.getContentType();
|
String resBodyRaw = HttpAuditSupport.bytesToString(res.getContentAsByteArray(), charset);
|
String resBodyToStore;
|
int truncated = 0;
|
if (respCt != null && (respCt.contains("octet-stream") || respCt.contains("application/pdf"))) {
|
resBodyToStore = "[binary response omitted]";
|
truncated = 1;
|
} else if (HttpAuditSupport.isQueryLike(req)) {
|
resBodyToStore = HttpAuditSupport.truncateForStore(resBodyRaw, props.getQueryResponseMaxChars());
|
if (resBodyRaw != null && resBodyRaw.length() > props.getQueryResponseMaxChars()) {
|
truncated = 1;
|
}
|
} else {
|
resBodyToStore = HttpAuditSupport.truncateForStore(resBodyRaw, props.getMaxResponseStoreChars());
|
if (resBodyRaw != null && resBodyRaw.length() > props.getMaxResponseStoreChars()) {
|
truncated = 1;
|
}
|
}
|
|
int status = res.getStatus();
|
int ok = (chainError == null && status >= 200 && status < 400) ? 1 : 0;
|
String errMsg = null;
|
if (chainError != null) {
|
String s = chainError.toString();
|
errMsg = s.length() > 4000 ? s.substring(0, 4000) + "..." : s;
|
}
|
|
String appName = environment.getProperty("spring.application.name", "unknown");
|
|
HttpAuditLog logEntity = new HttpAuditLog()
|
.setServiceName(appName)
|
.setScopeType(HttpAuditSupport.resolveScope(req, props))
|
.setUri(HttpAuditSupport.safePath(req))
|
.setMethod(req.getMethod())
|
.setFunctionDesc(HttpAuditSupport.resolveFunctionDesc(req, props))
|
.setQueryString(req.getQueryString())
|
.setRequestBody(reqBody)
|
.setResponseBody(resBodyToStore)
|
.setResponseTruncated(truncated)
|
.setHttpStatus(status)
|
.setOkFlag(ok)
|
.setSpendMs((int) (System.currentTimeMillis() - t0))
|
.setClientIp(HttpAuditSupport.clientIp(req))
|
.setErrorMessage(errMsg)
|
.setCreateTime(new Date())
|
.setDeleted(0);
|
|
recorder.save(logEntity);
|
}
|
}
|