You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 16:53:09