cl
4 小时以前 52e09a6b7b7054fc51b9d4bf5f1fbec0a57e60f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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);
    }
}