1

I know a similar problem has been posted several times on StackOverflow, still all the answers did not help in my case.

Target: I need to log the SOAP-Request XML into a database before processing it further.
For this discussion, I'm happy if I can get it as a String and log it to console.

Problem: Request XML is always empty.

Environment: IBM WebSphere Application Server v8.5, EJB 3.1 web service (session bean)

I actually produced a working solution using javax.xml.soap.SOAPBody and SOAPMessage, but on production there seems to be another component which causes the following JAX-WS conflict:

ERROR org.apache.axis2.engine.AxisEngine receive - com.sun.xml.messaging.saaj.soap.ver1_1.Body1_1Impl incompatible with com.ibm.ws.webservices.engine.xmlsoap.SOAPBody

Yes, there are multiple workarounds on StackOverflow and IBM about changing the ClassLoader policy, like "local class loader first (parent last)", but we currently can't go this way.
So, my current approach (which was working on a different servlet) is to get the HTTPServletRequest, get it's InputStream and convert it to a String using IOUtils.toString().

I am aware that a request can only be consumed once and I found several approaches to avoid it (like using a HTTPServletRequestWrapper) but even using these workarounds the request XML is always empty.
Eventually I want to do the logging in an adapter, but for testing reasons I also put my attempts into the service itself (it did not have any effect).

The strange thing is: I can read all Headers and Attributes from the request (using request.getHeader() and request.getAttribute()! Only the body itself is blank!

I'm testing the application using SoapUI.
Request-XML:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ping="http://services.axonactive.com/wsdl/cet/pingservice-v1.0">
  <soapenv:Header/>
  <soapenv:Body>
    <ping:PingPingIn/>
  </soapenv:Body>
</soapenv:Envelope>

Response is actually irrelevant, but correctly working.

Console output:

[ch.zek.ecls.EclsPingServiceImpl]: Initialization successful.
EclsPingServiceImpl ping - start ping()...
EclsPingServiceImpl ping - Body: 
EclsPingServiceImpl ping - body2: 
EclsUtil printRequestData - [H] Accept-Encoding: gzip,deflate
EclsUtil printRequestData - [H] Content-Type: text/xml;charset=UTF-8
EclsUtil printRequestData - [H] SOAPAction: ""
EclsUtil printRequestData - [H] Content-Length: 254
EclsUtil printRequestData - [H] Host: localhost:9443
EclsUtil printRequestData - [H] Connection: Keep-Alive
EclsUtil printRequestData - [H] User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
EclsUtil printRequestData - [A] javax.servlet.request.key_size: 128
EclsUtil printRequestData - [A] javax.servlet.request.cipher_suite: SSL_RSA_WITH_AES_128_CBC_SHA
EclsUtil printRequestData - [A] com.ibm.websphere.servlet.uri_non_decoded: /NewZekEclsHTTPRouter/PingService
EclsPingServiceImpl ping - end ping()

Edit: Note the headers [H] for Content-Type and Content-Length:
They "know" the content - if I put some more characters into the request XML then Content-Length is updated accordingly.
So I conclude that the content is not lost, but somehow not accessible... .

Webservice:

public class EclsPingServiceImpl{

@javax.annotation.Resource
WebServiceContext wsContext;

@javax.annotation.Resource
SessionContext sessionContext;

private Log log = LogFactory.getLog(EclsPingServiceImpl.class);

public PingOut ping(PingIn parameters) throws PingEntityNotFoundException, PingPermissionException, PingSystemException {

    MessageContext messageContext = wsContext.getMessageContext();
    HttpServletRequest request = (HttpServletRequest) messageContext.get(MessageContext.SERVLET_REQUEST);

    // start Try 1
    MultiReadHttpServletRequest multiReadHttpServletRequest = new MultiReadHttpServletRequest(request);
    try {
        InputStream bodyInputStream = multiReadHttpServletRequest.getInputStream();
        String body = IOUtils.toString(bodyInputStream);
        if (log.isDebugEnabled()) {
            log.debug("Body: " + body);
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    // end Try 1

    // start Try 2
    try {
        InputStream body2 = request.getInputStream();
        String xml = IOUtils.toString(body2, "UTF-8");
        if (log.isDebugEnabled()) {
            log.debug("body2: " + xml);
        }
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    // end Try 2

    // Get Header data:
    Enumeration<String> headerNames = request.getHeaderNames();
    while(headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement();
        if (log.isDebugEnabled()) {
            log.debug("[H] " + headerName + ": " + request.getHeader(headerName));
        }
    }

    // Get Attribute data:
    Enumeration<String> attributeNames = request.getAttributeNames();
    while(attributeNames.hasMoreElements()) {
        String attributeName = attributeNames.nextElement();
        if (log.isDebugEnabled()) {
            log.debug("[A] " + attributeName + ": " + request.getAttribute(attributeName));
        }
    }

    PingOut pingOut = new PingOut();
    // some irrelevant stuff...

    return pingOut;
}

}

MultiReadHttpServletRequestWrapper:
Copied from StackOverflow: Http Servlet request lose params from POST body after read it once

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
    private ByteArrayOutputStream cachedBytes;

    public MultiReadHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null)
            cacheInputStream();

        return new CachedServletInputStream();
    }

    @Override
    public BufferedReader getReader() throws IOException{
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    private void cacheInputStream() throws IOException {
        /* Cache the inputstream in order to read it multiple times. For
         * convenience, I use apache.commons IOUtils
         */
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    /* An inputstream which reads the cached request body */
    public class CachedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream input;

        public CachedServletInputStream() {
            /* create a new input stream from the cached request body */
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }

        @Override
        public int read() throws IOException {
            return input.read();
        }
    }
}

LogHandler - just to show what I tried, too...:

public class EclsSimpleLogHandler implements javax.xml.ws.handler.soap.SOAPHandler 
{
    private Log log = LogFactory.getLog(EclsSimpleLogHandler.class);

    @Override
    public boolean handleMessage(MessageContext context) {
        boolean success = false;

        if (log.isDebugEnabled()) {
            log.debug("handleMessage()...");
        }
        Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        // check if handler is called for inbound (request) or outbound (response) message
        if (outboundProperty.booleanValue()) {
            success = handleResponse(context);
        } else {
            //success = handleRequest(context);
            success = true;
        }
        return success;
    }

    private boolean handleRequest(MessageContext messageContext) {

        if(log.isDebugEnabled()) {
            log.debug("handling request (inbound)");
        }

        // Initially logging planned here, moved out for testing reasons
        boolean success = false;

        return success;     
    }

    private boolean handleResponse(MessageContext messageContext) {

        if(log.isDebugEnabled()) {
            log.debug("handling response (outbound)");
        }

        boolean success = false;
        ByteArrayOutputStream outputStream = null;

        SOAPMessageContext context = (SOAPMessageContext) messageContext;
        SOAPMessage soapMessage = (SOAPMessage) context.getMessage();

        try {
            /*
            Initial solution, but sometimes causing:
            ERROR org.apache.axis2.engine.AxisEngine receive - com.sun.xml.messaging.saaj.soap.ver1_1.Body1_1Impl 
            incompatible with com.ibm.ws.webservices.engine.xmlsoap.SOAPBody

            // SOAPBody soapBody = soapMessage.getSOAPBody();
            // String soapBodyXml = soapBody.toString();
            */

            // This is working - but I want to avoid using SOAPMessage:
            outputStream = new ByteArrayOutputStream();
            soapMessage.writeTo(outputStream);
            String soapBodyXml = new String(outputStream.toByteArray(), "UTF-8");

            if (log.isDebugEnabled()) {
                log.debug("responseXml:\n" + soapBodyXml);
            }

            success = true;

        } catch (SOAPException e) {
            if (log.isErrorEnabled()) {
                log.error("Error while accessing SOAPMessage: " + e.getMessage());
            }
        } catch (IOException e) {
            if (log.isErrorEnabled()) {
                log.error("IOException for soapMessage.writeTo(): " + e.getMessage());
            }
            e.printStackTrace();
        }
        return success;     
    }

    // Another method, but also resulting in an empty body:
    static String extractPostRequestBody(HttpServletRequest request) {
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            Scanner s = null;
            try {
                s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return s.hasNext() ? s.next() : "";
        }
        return "";
    }

    // Another method, but also resulting in an empty body:
    private String getRequestBody(HttpServletRequest request) {

        HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            InputStream inputStream = requestWrapper.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ex) {
            log.error("Error reading the request payload", ex);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException iox) {
                    // ignore
                }
            }
        }

        return stringBuilder.toString();
    }
}
Community
  • 1
  • 1
  • I had to use a workaround. Actual problem was the mentioned JAX-WS conflict with SOAPBody. We now log the entire SOAPMessage, for this class so far no classLoader problems occured. So I just prevented to use SOAPBody class. Works for me, still I don't understand why the Body of the HTTPServletRequest is always empty... therefore I added this hint as comment, not as solution. – Gerry Broennimann Jul 28 '16 at 14:05

0 Answers0