Spring Boot 返回值额外增加了一些字段

Spring Boot About 6,156 words

现象

状态码为:200

在统一的返回值后面,又额外加了timestampstatuserrorpath字段。

{
    "code": "0",
    "errorMsg": null,
    "data": {
        ...
    }
}{
    "timestamp": "2023-06-27T02:45:34.128+00:00",
    "status": 200,
    "error": "OK",
    "path": "/test"
}

排查

发现使用了zalandologbook脱敏组件。版本:2.14.0

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>2.14.0</version>
</dependency>

错误日志

[2023-06-27 14:16:56.836] [http-nio-8081-exec-2] [ERROR] [org.apache.juli.logging.DirectJDKLog : log : 175] [] [OpenAPI] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause 
java.lang.StackOverflowError: null
    at java.base/java.util.regex.Pattern$BranchConn.match(Pattern.java:4716)
    at java.base/java.util.regex.Pattern$Curly.match0(Pattern.java:4411)
    at java.base/java.util.regex.Pattern$Curly.match0(Pattern.java:4411)
    ...
    at java.base/java.util.regex.Pattern$Curly.match0(Pattern.java:4411)
    ...
    at java.base/java.util.regex.Matcher.find(Matcher.java:746)
    at org.zalando.logbook.json.PrimitiveJsonPropertyBodyFilter.filter(PrimitiveJsonPropertyBodyFilter.java:83)
    at org.zalando.logbook.NonMergeableBodyFilterPair.filter(NonMergeableBodyFilterPair.java:21)
    at org.zalando.logbook.FilteredHttpResponse.getBodyAsString(FilteredHttpResponse.java:50)
    at org.zalando.logbook.json.JsonHttpLogFormatter.prepareBody(JsonHttpLogFormatter.java:64)
    at org.zalando.logbook.StructuredHttpLogFormatter.prepare(StructuredHttpLogFormatter.java:95)
    at org.zalando.logbook.StructuredHttpLogFormatter.format(StructuredHttpLogFormatter.java:26)
    at org.zalando.logbook.DefaultSink.write(DefaultSink.java:26)
    at org.zalando.logbook.Strategy.write(Strategy.java:119)
    at org.zalando.logbook.DefaultLogbook$1.lambda$process$0(DefaultLogbook.java:55)
    at org.zalando.logbook.servlet.LogbookFilter.write(LogbookFilter.java:87)
    at org.zalando.logbook.servlet.LogbookFilter.doFilter(LogbookFilter.java:80)
    at org.zalando.logbook.servlet.HttpFilter.doFilter(HttpFilter.java:31)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)

原因

对于大的JSON返回值,会一直在while循环中,从而导致StackOverflowError,从而走到SpringMVCErrorController逻辑,额外增加了字段。

logbook

// org.zalando.logbook.json.PrimitiveJsonPropertyBodyFilter#filter
@Override
public String filter(@Nullable final String contentType, final String body) {
    if (JsonMediaType.JSON.test(contentType)) {
        final Matcher matcher = pattern.matcher(body);
        final StringBuffer result = new StringBuffer(body.length());

        while (matcher.find()) {
            if (predicate.test(matcher.group("property"))) {
                // this preserves whitespaces around properties
                matcher.appendReplacement(result, "${key}");
                result.append(replacement.apply(
                        matcher.group("property"),
                        matcher.group("propertyValue")));
            } else {
                matcher.appendReplacement(result, "$0");
            }
        }
        matcher.appendTail(result);

        return result.toString();
    }
    return body;
}

BasicErrorController & DefaultErrorAttributes

// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
        return new ResponseEntity<>(body, status);
    }
}

// org.springframework.boot.web.servlet.error.DefaultErrorAttributes#getErrorAttributes(org.springframework.web.context.request.WebRequest, boolean)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }
        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }
        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.remove("message");
        }
        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }
        return errorAttributes;
    }

    private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        addStatus(errorAttributes, webRequest);
        addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

}

@ControllerAdvice

对于这个问题使用@ControllerAdvice,并不能抓到Throwable的异常。

参考

https://github.com/zalando/logbook/pull/1343/files

Views: 1,171 · Posted: 2023-07-10

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb/LiteNote

扫描下方二维码关注公众号和小程序↓↓↓

扫描下方二维码关注公众号和小程序↓↓↓


Today On History
Browsing Refresh