0

I’m user of CXF in an SOA development context.

I’m wondering if my problem has a solution with CXF. Here’s my need. We have developed a webapp serving JAXWS endpoints, the endpoint implementations consists in analyzing the request via interceptors, storing data from the request to a database from the service layer in Java, and resend the original request to another server via a CXF client. The point is that some of our requests contains a DSIG signature (https://www.w3.org/TR/xmldsig-core/) or an signed SAML Assertion. Our need is to resend the requests without altering them (such as a proxy) from a CXFClient. CXF uses to send the marshalled object to the server, but this way the original stream is not sent

Is there a way to resend the incoming request from the service layer from a Java CXFClient without altering it (the Signatures depends on the format of the request: blanks, namespaces prefixes, carriage returns…) ? We prefer the CXFClient because we would like to reuse our homemade CXF interceptor which logs the outgoing request.

We have tested an interceptor to intend to replace the outputStream by the original request before sending it to the server, we used this answer: How To Modify The Raw XML message of an Outbound CXF Request?, but we are still KO, CXF still sends the stream made from the marshalled object. See code below.

Context: - CXF 2.7.18 (JDK 6), and 3.1.10 (JDK 8) - Platform: windows 7 64bit/ rhel 7 64bit - Apache Tomcat 7 - Tcpdump to analyse incoming traffic

Sample of code of our client:

    final Client cxfClient = org.apache.cxf.frontend.ClientProxy.getClient( portType );
    cxfClient.getInInterceptors().clear();
cxfClient.getOutInterceptors().clear();
cxfClient.getOutFaultInterceptors().clear();
cxfClient.getRequestContext().put(CustomStreamerInterceptor.STREAM_TO_SEND,
PhaseInterceptorChain.getCurrentMessage().getContent( InputStream.class ) );
cxfClient.getOutInterceptors().add( new CustomStreamerInterceptor() );
org.apache.cxf.transport.http.HTTPConduit http = (org.apache.cxf.transport.http.HTTPConduit) cxfClient.getConduit();
...
port.doSomething(someRequest);

CustomStreamerInterceptor:

package test;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapOutInterceptor.SoapOutEndingInterceptor;
import org.apache.cxf.helpers.LoadingByteArrayOutputStream;
import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CacheAndWriteOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.Phase;
public class CustomStreamerInterceptor extends AbstractOutDatabindingInterceptor {
       public static final String STREAM_TO_SEND = "STREAM_TO_SEND";
       public CustomStreamerInterceptor () {
             super( Phase.WRITE_ENDING );
             addAfter( SoapOutEndingInterceptor.class.getName() );
       }
       @Override
       public void handleMessage( Message message ) throws Fault {
             try {
                    InputStream toSend = (InputStream) message.get( STREAM_TO_SEND );
                    if ( toSend != null ) {
                           toSend.reset();
                           LoadingByteArrayOutputStream lBos = new LoadingByteArrayOutputStream();
                           IOUtils.copy( toSend, lBos );
                           CacheAndWriteOutputStream cawos = (CacheAndWriteOutputStream) message.getContent( OutputStream.class );
                           cawos.resetOut( lBos, false );//fail !
                    }
             }
             catch ( Exception e ) {
                    throw new Fault( e );
             }
       }
}

Thanks you for any help, it would be very usefull.

Community
  • 1
  • 1
Jeff3181
  • 1
  • 2

1 Answers1

0

I think it's better to create a "classic" HTTP client because CXF is not designed for that kind of situation, it's more common to use it to marshal objects from java to XML... Fortunately for you I deal with this problem with an interceptor. You can write an interceptor that copy the stream in the output stream object that CXF is preparing to send to the server. You need to be carefull to the phase and the order of you interceptors because if you use a Logging interceptor you may want to log the outgoing stream. This interceptor may do the job, be sure it runs after any logging interceptor. Code for CXF 2.7.18 :

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang.CharEncoding;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.StaxOutInterceptor;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PassePlatClientInterceptorOut extends AbstractPhaseInterceptor<Message> {
private static final Logger LOG = LoggerFactory.getLogger( PassePlatClientInterceptorOut.class );

private final Exchange exchangeToReadFrom;

public PassePlatClientInterceptorOut( final Exchange exchange ) {
    super( Phase.PRE_STREAM );
    addBefore( StaxOutInterceptor.class.getName() );
    this.exchangeToReadFrom = exchange;
}

@Override
public void handleMessage( Message message ) {
    InputStream is = (InputStream) exchangeToReadFrom.get( PassePlatServerInterceptorIn.PASSE_PLAT_INTERCEPTOR_STREAM_SERVEUR );
    if ( is != null ) {
        message.put( org.apache.cxf.message.Message.ENCODING, CharEncoding.UTF_8 );
        OutputStream os = message.getContent( OutputStream.class );
        try {
            IOUtils.copy( is, os );
            is.close();
        }
        catch ( IOException e ) {
            LOG.error( "Error ...", e );
            message.setContent( Exception.class, e );
            throw new Fault( new Exception( "Error ...", e ) );
        }
        boolean everythingOK = message.getInterceptorChain().doInterceptStartingAt( message,
                org.apache.cxf.interceptor.MessageSenderInterceptor.MessageSenderEndingInterceptor.class.getName() );
        if ( !everythingOK ) {
            LOG.error( "Error ?" );
            throw new Fault( new Exception( "Error ..." ) );
        }
    }
}

}

To create the interceptor:

cxfClient.getInInterceptors().add( new PassePlatClientInterceptorIn( exchange ) );