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

如何在Apache CXF中获取400/500状态码下的响应体

解决CXF客户端获取400响应中SOAP Fault内容的方案

核心问题分析

你用错了拦截器的阶段和类型:SEND阶段的OutInterceptor负责处理请求发送流程,而第三方服务的400响应属于接收响应环节,应该用InInterceptor并选择合适的接收阶段,同时要解决输入流只能读取一次的问题。

具体实现步骤

1. 自定义InInterceptor(缓存响应流+解析Fault)

创建拦截器在响应流刚被接收时缓存内容,故障发生时从缓存读取并解析SOAP Fault:

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class SoapFaultInterceptor extends AbstractPhaseInterceptor<Message> {

    public SoapFaultInterceptor() {
        // 选择POST_STREAM阶段:响应流已接收但未被解析,适合缓存
        super(Phase.POST_STREAM);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        InputStream is = message.getContent(InputStream.class);
        if (is == null) return;

        try {
            // 把流转换成字节数组缓存,避免流被消费后无法读取
            byte[] responseBytes = IOUtils.toByteArray(is);
            // 将可重复读取的流放回message
            message.setContent(InputStream.class, new ByteArrayInputStream(responseBytes));
            // 把字节数组存入message属性,供handleFault调用
            message.put("CACHED_RESPONSE_BODY", responseBytes);
        } catch (Exception e) {
            throw new Fault(e);
        }
    }

    @Override
    public void handleFault(Message message) {
        byte[] cachedBody = (byte[]) message.get("CACHED_RESPONSE_BODY");
        if (cachedBody == null) return;

        try {
            // 转换成XML字符串,解析SOAP Fault内容
            String soapFaultXml = new String(cachedBody, "UTF-8");
            
            // 这里可以用DOM/XPath/JAXB解析Fault的具体字段(示例用打印演示)
            System.out.println("原始SOAP Fault响应:\n" + soapFaultXml);
            
            // 可选:将解析后的Fault信息替换到异常中
            Fault currentFault = (Fault) message.getContent(Exception.class);
            // 比如用XPath提取faultstring:
            // Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(cachedBody));
            // String faultMsg = XPathFactory.newInstance().newXPath().evaluate("//soapenv:Fault/faultstring", doc);
            // currentFault.setMessage(faultMsg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. 给CXF客户端添加拦截器

将自定义拦截器加入客户端的入站拦截器链:

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPClientPolicy;

public class WebServiceClientFactory {
    public static <T> T createClient(Class<T> serviceInterface, String serviceUrl) {
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setServiceClass(serviceInterface);
        factory.setAddress(serviceUrl);

        // 添加自定义拦截器
        factory.getInInterceptors().add(new SoapFaultInterceptor());

        // 可选配置:让CXF不直接抛出400异常,而是继续处理响应体
        HTTPConduit conduit = (HTTPConduit) factory.create().getConduit();
        HTTPClientPolicy policy = new HTTPClientPolicy();
        policy.setHandleResponseCode(true);
        conduit.setClient(policy);

        return (T) factory.create();
    }
}

关键说明

  • 阶段选择:POST_STREAM是最适合缓存响应流的阶段,此时响应已被接收但未被CXF解析,不会出现流被提前消费的问题。
  • 流缓存:用字节数组缓存流内容,确保故障发生时能重复读取。
  • 可选配置handleResponseCode(true):让CXF不因为400状态码直接抛出HTTPException,而是继续解析响应体中的SOAP Fault,部分场景下无需自定义拦截器即可获取Fault信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 18:40:13