You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

使用Interceptor记录REST服务,无法获取请求与响应体

解决CXF拦截器无法获取JSON请求/响应体的问题

你已经搭好了拦截器的基础框架,但因为拦截器阶段选择和流处理的细节问题,没法拿到请求/响应体。下面我会一步步告诉你怎么调整代码来实现需求:

一、获取JSON请求体(入站拦截器)

你当前的入站拦截器用的是Phase.PRE_INVOKE阶段,这个阶段CXF已经把请求流解析成Java对象了,原始的请求流已经被消耗掉,自然拿不到内容。我们需要把拦截器阶段调整到Phase.POST_STREAM——这个阶段请求流已经被接收,但还没被CXF解析,正好适合读取请求体。

修改ContextJSONInInterceptor的代码:

import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedInputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class ContextJSONInInterceptor extends AbstractPhaseInterceptor<Message> {
    public ContextJSONInInterceptor() {
        // 调整到POST_STREAM阶段
        super(Phase.POST_STREAM);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        InputStream is = message.getContent(InputStream.class);
        if (is != null) {
            try {
                // 确保流是可重置的,如果不是就用CachedInputStream包装
                if (!is.markSupported()) {
                    is = new CachedInputStream(is);
                    message.setContent(InputStream.class, is);
                }
                // 读取请求体内容
                String requestBody = IOUtils.toString(is, StandardCharsets.UTF_8);
                System.out.println("Request Body: " + requestBody);
                
                // 重置流,让后续CXF的解析逻辑能正常读取
                is.reset();
            } catch (IOException e) {
                throw new Fault(new RuntimeException("Failed to read request body", e));
            }
        }

        // 修正原代码的错误:要从message中获取实际的Content-Type,而不是直接打印常量
        System.out.println("Content-Type: " + message.get(Message.CONTENT_TYPE));
        System.out.println("---------------");
    }
}

二、获取JSON响应体(出站拦截器)

出站的情况更特殊:响应流是只写的,直接读取会失败。我们需要用CXF提供的CachedOutputStream来缓存响应内容,等响应写完后再读取缓存的内容。同时,拦截器阶段要调整到Phase.PRE_STREAM,确保在响应写入流之前就完成包装。

修改ContextJSONOutInterceptor的代码:

import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class ContextJSONOutInterceptor extends AbstractPhaseInterceptor<Message> {
    public ContextJSONOutInterceptor() {
        // 调整到PRE_STREAM阶段
        super(Phase.PRE_STREAM);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        OutputStream os = message.getContent(OutputStream.class);
        if (os != null) {
            // 用CachedOutputStream包装原输出流,缓存所有写入的内容
            CachedOutputStream cos = new CachedOutputStream(os);
            message.setContent(OutputStream.class, cos);

            // 添加一个在SEND_ENDING阶段执行的拦截器,此时响应内容已经全部写入缓存
            message.getInterceptorChain().add(new AbstractPhaseInterceptor<Message>(Phase.SEND_ENDING) {
                @Override
                public void handleMessage(Message message) throws Fault {
                    try {
                        // 读取缓存的响应体内容
                        String responseBody = IOUtils.toString(cos.getInputStream(), StandardCharsets.UTF_8);
                        System.out.println("Response Body: " + responseBody);

                        // 将缓存的内容写回原输出流,确保客户端能收到响应
                        cos.writeCacheTo(os);
                        // 关闭缓存流
                        cos.close();
                    } catch (IOException e) {
                        throw new Fault(new RuntimeException("Failed to read response body", e));
                    }
                }
            });
        }

        // 修正原代码的错误:获取实际的Content-Type
        System.out.println("Content-Type: " + message.get(Message.CONTENT_TYPE));
        System.out.println("---------------");
    }
}

三、关键注意事项

  • 拦截器阶段选择:入站用POST_STREAM、出站用PRE_STREAM是核心,选错阶段要么流已经被消耗,要么内容还没生成。
  • 流的重置/缓存:读取请求流后必须重置,否则后续CXF解析会报错;响应流必须用CachedOutputStream包装,才能实现“先写后读”。
  • 依赖确保:确保你的项目中包含cxf-core包(提供CachedInputStream/CachedOutputStream)和commons-io包(提供IOUtils,也可以自己实现流读取逻辑)。

内容的提问来源于stack exchange,提问作者Caznik

火山引擎 最新活动