如何在Logbook拦截前预处理HTTP响应JSON冗余字段?
实现自定义响应处理后再由Logbook记录的方案
需求可行性
完全可以实现,核心是调整拦截器执行顺序并包装响应对象修改内容,确保Logbook最终处理的是清理后的响应。
问题根源
当前拦截器添加顺序错误:自定义拦截器先于Logbook拦截器添加,导致响应返回时先经过Logbook(已记录原始响应),再到自定义拦截器,修改无法被Logbook捕获。RestClient的拦截器执行逻辑是:
- 请求阶段:按添加顺序依次执行拦截器
- 响应阶段:按添加顺序的反向处理响应(最后添加的拦截器先处理响应)
解决方案步骤
1. 调整拦截器添加顺序
将Logbook拦截器放在自定义拦截器之前添加,这样响应返回时自定义拦截器会先处理内容,再传递给Logbook记录:
@Configuration public class ClientConfiguration { @Bean public SomeApiClient apiClient(RestClient.Builder restClientBuilder, Logbook logbook, @Value("${api.external.service.url}") String url, ObjectMapper objectMapper, LogbookRegistry logbookRegistry) { var restClient = restClientBuilder.clone() .baseUrl(url) .requestInterceptor(new LogbookClientHttpRequestInterceptor(logbook)) // 先添加Logbook拦截器 .requestInterceptor(new CustomJsonInterceptor(objectMapper, logbookRegistry)) // 后添加自定义拦截器 .defaultStatusHandler(new CustomHttpErrorHandler()) .build(); RestClientAdapter adapter = RestClientAdapter.create(restClient); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); return factory.createClient(SomeApiClient.class); } }
2. 自定义ClientHttpResponse包装类
用于包装原始响应,替换body为清理后的JSON内容(解决响应流只能读取一次的问题):
public class ModifiedClientHttpResponse implements ClientHttpResponse { private final ClientHttpResponse originalResponse; private final byte[] modifiedBody; private final ObjectMapper objectMapper; public ModifiedClientHttpResponse(ClientHttpResponse originalResponse, ObjectMapper objectMapper, Set<String> fieldsToRemove) throws IOException { this.originalResponse = originalResponse; this.objectMapper = objectMapper; // 读取原始响应并移除指定字段 JsonNode originalJson = objectMapper.readTree(originalResponse.getBody()); Iterator<String> fieldNames = originalJson.fieldNames(); while (fieldNames.hasNext()) { String fieldName = fieldNames.next(); if (fieldsToRemove.contains(fieldName)) { fieldNames.remove(); } } // 转换为修改后的字节数组 this.modifiedBody = objectMapper.writeValueAsBytes(originalJson); } @Override public HttpStatus getStatusCode() throws IOException { return originalResponse.getStatusCode(); } @Override public int getRawStatusCode() throws IOException { return originalResponse.getRawStatusCode(); } @Override public String getStatusText() throws IOException { return originalResponse.getStatusText(); } @Override public void close() { originalResponse.close(); } @Override public HttpHeaders getHeaders() { // 更新Content-Length头部,避免长度不匹配 HttpHeaders headers = new HttpHeaders(); headers.putAll(originalResponse.getHeaders()); headers.setContentLength(modifiedBody.length); return headers; } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(modifiedBody); } }
3. 修改CustomJsonInterceptor逻辑
在拦截器中获取原始响应,用包装类处理后返回,确保后续拦截器(Logbook)拿到的是修改后的响应:
public class CustomJsonInterceptor implements ClientHttpRequestInterceptor { private final ObjectMapper objectMapper; private final LogbookRegistry logbookRegistry; // 可根据需求配置需要移除的字段,示例为硬编码,实际可从配置文件读取 private final Set<String> fieldsToRemove = Set.of("sensitiveField", "unnecessaryField"); public CustomJsonInterceptor(ObjectMapper objectMapper, LogbookRegistry logbookRegistry) { this.objectMapper = objectMapper; this.logbookRegistry = logbookRegistry; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 执行后续拦截器和实际请求,获取原始响应 ClientHttpResponse originalResponse = execution.execute(request, body); // 包装响应并返回修改后的版本 return new ModifiedClientHttpResponse(originalResponse, objectMapper, fieldsToRemove); } }
关键说明
- 拦截器顺序:必须保证Logbook拦截器先添加,这样响应阶段自定义拦截器先处理,Logbook后处理修改后的内容。
- 响应流处理:由于HTTP响应流只能读取一次,必须通过包装类提前读取并修改内容,再提供新的输入流给后续拦截器。
- 头部更新:修改body后要同步更新
Content-Length头部,避免出现长度不匹配的问题。
内容的提问来源于stack exchange,提问作者Skorosnoyj
相关产品推荐
相关产品推荐

