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); } }