如何修改出站CXF请求的原始XML消息?

22

我想修改一个即将发送的SOAP请求,删除信封正文中的两个xml节点。我已经设置了拦截器,并获得了发送到终端点的消息生成字符串值。

然而,以下代码似乎无法按预期编辑即将发送的消息。有没有人有一些代码或想法来完成这个任务?

public class MyOutInterceptor extends AbstractSoapInterceptor {

public MyOutInterceptor() {
        super(Phase.SEND); 
}

public void handleMessage(SoapMessage message) throws Fault { 
        // Get message content for dirty editing...
        StringWriter writer = new StringWriter();
        CachedOutputStream cos  = (CachedOutputStream)message.getContent(OutputStream.class); 
        InputStream inputStream = cos.getInputStream();
        IOUtils.copy(inputStream, writer, "UTF-8");
        String content = writer.toString();

        // remove the substrings from envelope...
        content = content.replace("<idJustification>0</idJustification>", "");
        content = content.replace("<indicRdv>false</indicRdv>", "");
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(content.getBytes(Charset.forName("UTF-8")));
        message.setContent(OutputStream.class, outputStream);
} 
6个回答

43

根据第一个评论,我创建了一个抽象类,可以轻松地用来更改整个SOAP信封。

以防有人想要一个可直接使用的代码部分。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
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 org.apache.log4j.Logger;

/**
 * http://www.mastertheboss.com/jboss-web-services/apache-cxf-interceptors
 * https://dev59.com/Omw05IYBdhLWcg3w9mnd
 * 
 */
public abstract class MessageChangeInterceptor extends AbstractPhaseInterceptor<Message> {

    public MessageChangeInterceptor() {
        super(Phase.PRE_STREAM);
        addBefore(SoapPreProtocolOutInterceptor.class.getName());
    }

    protected abstract Logger getLogger();

    protected abstract String changeOutboundMessage(String currentEnvelope);

    protected abstract String changeInboundMessage(String currentEnvelope);

    public void handleMessage(Message message) {
        boolean isOutbound = false;
        isOutbound = message == message.getExchange().getOutMessage()
                || message == message.getExchange().getOutFaultMessage();

        if (isOutbound) {
            OutputStream os = message.getContent(OutputStream.class);

            CachedStream cs = new CachedStream();
            message.setContent(OutputStream.class, cs);

            message.getInterceptorChain().doIntercept(message);

            try {
                cs.flush();
                IOUtils.closeQuietly(cs);
                CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);

                String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
                csnew.flush();
                IOUtils.closeQuietly(csnew);

                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Outbound message: " + currentEnvelopeMessage);
                }

                String res = changeOutboundMessage(currentEnvelopeMessage);
                if (res != null) {
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug("Outbound message has been changed: " + res);
                    }
                }
                res = res != null ? res : currentEnvelopeMessage;

                InputStream replaceInStream = IOUtils.toInputStream(res, "UTF-8");

                IOUtils.copy(replaceInStream, os);
                replaceInStream.close();
                IOUtils.closeQuietly(replaceInStream);

                os.flush();
                message.setContent(OutputStream.class, os);
                IOUtils.closeQuietly(os);

            } catch (IOException ioe) {
                getLogger().warn("Unable to perform change.", ioe);
                throw new RuntimeException(ioe);
            }
        } else {
            try {
                InputStream is = message.getContent(InputStream.class);
                String currentEnvelopeMessage = IOUtils.toString(is, "UTF-8");
                IOUtils.closeQuietly(is);

                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Inbound message: " + currentEnvelopeMessage);
                }

                String res = changeInboundMessage(currentEnvelopeMessage);
                if (res != null) {
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug("Inbound message has been changed: " + res);
                    }
                }
                res = res != null ? res : currentEnvelopeMessage;

                is = IOUtils.toInputStream(res, "UTF-8");
                message.setContent(InputStream.class, is);
                IOUtils.closeQuietly(is);
            } catch (IOException ioe) {
                getLogger().warn("Unable to perform change.", ioe);

                throw new RuntimeException(ioe);
            }
        }
    }

    public void handleFault(Message message) {
    }

    private class CachedStream extends CachedOutputStream {
        public CachedStream() {
            super();
        }

        protected void doFlush() throws IOException {
            currentStream.flush();
        }

        protected void doClose() throws IOException {
        }

        protected void onWrite() throws IOException {
        }
    }
}

1
CachedStream是从哪里来的?我没有看到它的导入并且找不到它。 - javamonkey79
Cached Stream是apache SVN示例中的一个内部类。http://svn.apache.org/viewvc/cxf/trunk/distribution/src/main/release/samples/configuration_interceptor/src/main/java/demo/stream/interceptor/StreamInterceptor.java?view=markup提供的抽象类非常好用! :) - questionaire
1
为什么这段代码在处理更大的消息时会失败?message.getInterceptorChain().doIntercept(message) 这一行会使消息内容变成 null - shx
3
如果有人想知道如何将此附加到生成的 Apache Cxf 客户端上,只需使用 Client client = ClientProxy.getClient(someServicePort); client.getInInterceptors().add(new MessageChangeInterceptor() { .. }); 即可,这样就可以修改原始响应。 - Boris Treukhov
2
很抱歉... 这是一段非常糟糕的代码。它可能能够工作,但像@shx经历的那样,这只是纯属运气。输入和输出流被无缘由地创建、在空时被刷新、因无原因而被交换到消息中、在作者未打开它们的情况下被关闭(这意味着流可能被重复关闭),等等。此外,如果消息写入是通过writer 而不是output stream完成的(例如messageType = TEXT的JMS上的SOAP),它根本无法工作。基本原则是好的(使用interceptor访问流...),但实现真的是错得离谱。 - GPI
显示剩余2条评论

20

今天我也遇到了这个问题。经过长时间的尝试和困扰,我最终能够修改 CXF 源码中 configuration_interceptor 示例中的 StreamInterceptor 类:

OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);

message.getInterceptorChain().doIntercept(message);

try {
    cs.flush();
    CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);

    String soapMessage = IOUtils.toString(csnew.getInputStream());
    ...

soapMessage变量将包含完整的SOAP消息。您应该能够操作SOAP消息,将其刷新到输出流中,并进行message.setContent(OutputStream.class...调用以将您的修改放入消息中。此功能没有保证,因为我自己对CXF还很新!

注意:CachedStream是StreamInterceptor类中的私有类。不要忘记将拦截器配置为在PRE_STREAM阶段运行,以便SOAP拦截器有机会编写SOAP消息。


谢谢你的回答,John。与该问题相关的其他元素可以在此处找到:https://dev59.com/c2w05IYBdhLWcg3w_Guw - kiwifrog
在你的例子中,声明了OutputStream os,但没有使用:你在这段代码中需要它吗? - Alessandro C

2
以下是能够将服务器端异常冒泡的内容。在之前的解决方案中使用os.close()而不是IOUtils.closeQuietly(os)也可以使异常冒泡。
public class OutInterceptor extends AbstractPhaseInterceptor<Message> {     
    public OutInterceptor() { 
        super(Phase.PRE_STREAM); 
        addBefore(StaxOutInterceptor.class.getName()); 
    }   
    public void handleMessage(Message message) { 
        OutputStream os = message.getContent(OutputStream.class); 
        CachedOutputStream cos = new CachedOutputStream(); 
        message.setContent(OutputStream.class, cos); 
        message.getInterceptorChain.aad(new PDWSOutMessageChangingInterceptor(os)); 
    }
} 

public class OutMessageChangingInterceptor extends AbstractPhaseInterceptor<Message> {
    private OutputStream os; 

    public OutMessageChangingInterceptor(OutputStream os){
        super(Phase.PRE_STREAM_ENDING); 
        addAfter(StaxOutEndingInterceptor.class.getName()); 
        this.os = os;
    } 

    public void handleMessage(Message message) { 
        try { 
            CachedOutputStream csnew = (CachedOutputStream) message .getContent(OutputStream.class);
            String currentEnvelopeMessage = IOUtils.toString( csnew.getInputStream(), (String) message.get(Message.ENCODING)); 
            csnew.flush(); 
            IOUtils.closeQuietly(csnew); 
            String res = changeOutboundMessage(currentEnvelopeMessage); 
            res = res != null ? res : currentEnvelopeMessage; 
            InputStream replaceInStream = IOUtils.tolnputStream(res, (String) message.get(Message.ENCODING)); 
            IOUtils.copy(replaceInStream, os); 
            replaceInStream.close(); 
            IOUtils.closeQuietly(replaceInStream);
            message.setContent(OutputStream.class, os);
        } catch (IOException ioe) {
            throw new RuntimeException(ioe);  
        }
    }
} 

2
欢迎来到Stack Overflow!虽然这个答案可能是正确和有用的,但最好还是附上一些解释,以说明它如何帮助解决问题。如果将来发生了变化(可能与此无关),导致它停止工作,用户需要了解它曾经是如何工作的,这时解释就变得尤为重要。 - Kevin Brown-Silva

1
更好的方法是使用DOM接口修改消息,您需要首先添加SAAJOutInterceptor(这可能会对大型请求产生性能影响),然后再添加在USER_PROTOCOL阶段执行的自定义拦截器。
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Node;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

abstract public class SoapNodeModifierInterceptor extends AbstractSoapInterceptor {
    SoapNodeModifierInterceptor() { super(Phase.USER_PROTOCOL); }

    @Override public void handleMessage(SoapMessage message) throws Fault {
        try {
            if (message == null) {
                return;
            }
            SOAPMessage sm = message.getContent(SOAPMessage.class);
            if (sm == null) {
                throw new RuntimeException("You must add the SAAJOutInterceptor to the chain");
            }

            modifyNodes(sm.getSOAPBody());

        } catch (SOAPException e) {
            throw new RuntimeException(e);
        }
    }

    abstract void modifyNodes(Node node);
}

1

根据this替换出站SOAP内容的良好示例

package kz.bee.bip;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

public class SOAPOutboundInterceptor extends AbstractPhaseInterceptor<Message> {

    public SOAPOutboundInterceptor() {
        super(Phase.PRE_STREAM);
        addBefore(SoapPreProtocolOutInterceptor.class.getName());
    }

    public void handleMessage(Message message) {

        boolean isOutbound = false;
        isOutbound = message == message.getExchange().getOutMessage()
                || message == message.getExchange().getOutFaultMessage();

        if (isOutbound) {
            OutputStream os = message.getContent(OutputStream.class);
            CachedStream cs = new CachedStream();
            message.setContent(OutputStream.class, cs);

            message.getInterceptorChain().doIntercept(message);

            try {
                cs.flush();
                IOUtils.closeQuietly(cs);
                CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);

                String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
                csnew.flush();
                IOUtils.closeQuietly(csnew);

                /* here we can set new data instead of currentEnvelopeMessage*/
                InputStream replaceInStream = IOUtils.toInputStream(currentEnvelopeMessage, "UTF-8");

                IOUtils.copy(replaceInStream, os);
                replaceInStream.close();
                IOUtils.closeQuietly(replaceInStream);

                os.flush();
                message.setContent(OutputStream.class, os);
                IOUtils.closeQuietly(os);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    public void handleFault(Message message) {
    }

    private static class CachedStream extends CachedOutputStream {
        public CachedStream() {
            super();
        }

        protected void doFlush() throws IOException {
            currentStream.flush();
        }

        protected void doClose() throws IOException {
        }

        protected void onWrite() throws IOException {
        }
    }
}


1
这个对我很有用。它基于Apache CXF示例中的configuration_interceptor中的StreamInterceptor类。它是用Scala而不是Java编写的,但转换很简单。 我试图添加注释来解释正在发生的事情(就我所理解的而言)。
import java.io.OutputStream

import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor
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

// java note: base constructor call is hidden at the end of class declaration
class StreamInterceptor() extends AbstractPhaseInterceptor[Message](Phase.PRE_STREAM) {

  // java note: put this into the constructor after calling super(Phase.PRE_STREAM);
  addBefore(classOf[SoapPreProtocolOutInterceptor].getName)

  override def handleMessage(message: Message) = {
    // get original output stream
    val osOrig = message.getContent(classOf[OutputStream])
    // our output stream
    val osNew = new CachedOutputStream
    // replace it with ours
    message.setContent(classOf[OutputStream], osNew)
    // fills the osNew instead of osOrig
    message.getInterceptorChain.doIntercept(message)
    // flush before getting content
    osNew.flush()
    // get filled content
    val content = IOUtils.toString(osNew.getInputStream, "UTF-8")
    // we got the content, we may close our output stream now
    osNew.close()
    // modified content
    val modifiedContent = content.replace("a-string", "another-string")
    // fill original output stream
    osOrig.write(modifiedContent.getBytes("UTF-8"))
    // flush before set
    osOrig.flush()
    // replace with original output stream filled with our modified content
    message.setContent(classOf[OutputStream], osOrig)
  }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接