0

I'm using the apache cxf library as our SOAP client to transmit SOAP requests to a specific endpoint. Unfortunately it seems that this endpoint do have some trouble to correctly parse the data if it comes in one line (which is pretty much the standard). I've written a simple Test-Client which sends RAW Strings to that endpoint and with that I was able to find out that the requests could be processed if the SOAP-Message is pretty printed. If I send the very same request in one line the server respond with a HTTP/1.1 500 Internal Server Error.

I've already submitted a bug report but I'm afraid that the receiving company will ask me to transmit my data pretty printed.

Is that possible with apache cxf?

Martin Fernau
  • 787
  • 1
  • 6
  • 19
  • 1
    You could try using [interceptors](https://cxf.apache.org/docs/interceptors.html) to transform the response before sending, but are you sure it's just the formatting of the message and no other difference between the requests that work and the ones that don't? The soap web service would be fundamentally broken if it could not parse a proper message that isn't pretty formatted (something for which only humans would care anyways). – Bogdan Jul 28 '21 at 08:55
  • Yes I'm sure. The whole thing is little more complex as described to be honest. All works fine until I transmit a "Authorization" HTTP-Header to the remote system. With that header the remote system broke and send status code 500. As it appears to me (inferred from the error message I got) a transformation scenario on the remote side tries to modify my original request which seems to fail if all comes in one line. If I change my request and add at least two newlines (at specific places) all is working fine again. For me a very clear indicator that the remote system has a bug – Martin Fernau Jul 28 '21 at 12:11

1 Answers1

1

I've found another question which helped me to find a solution for my needs.

Going from that post I was able to adopt the following code which can do what I need

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
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 PrettyPrintingOutInterceptor extends AbstractPhaseInterceptor<Message> {

    public PrettyPrintingOutInterceptor(int indent) {
        super(Phase.PRE_STREAM);
        addBefore(SoapPreProtocolOutInterceptor.class.getName());
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        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();
                CachedOutputStream csnew = (CachedOutputStream) message
                    .getContent(OutputStream.class);
                
                // get current payload
                String soapMessage = IOUtils.toString(csnew.getInputStream());
                // manipulate payload
                soapMessage = prettyPrint(soapMessage, 3);
                
                // Write new data into the OutputStream from above
                ByteArrayInputStream bin = new ByteArrayInputStream(soapMessage.getBytes());
                CachedOutputStream.copyStream(bin, os, 1024);

                os.flush();
            } catch (IOException | TransformerException e) {
                // error handling
            } finally {
                // Important! Close streams!
                try {
                    cs.close();
                } catch (IOException e) {
                }
                try {
                    os.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private String prettyPrint(String xml, int indent) throws TransformerException {
        Source xmlInput = new StreamSource(new StringReader(xml));
        StringWriter stringWriter = new StringWriter();
        StreamResult xmlOutput = new StreamResult(stringWriter);
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer(); 
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent);
        transformer.transform(xmlInput, xmlOutput);
        return xmlOutput.getWriter().toString();
    }

    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 {
        }
    }
}

and adding an object from this class to the client with client.getOutInterceptors().add(new PrettyPrintingOutInterceptor());

Martin Fernau
  • 787
  • 1
  • 6
  • 19