Announcement Announcement Module
Collapse
No announcement yet.
Creating a streaming web service with Spring-WS Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Creating a streaming web service with Spring-WS

    I am creating a web service which produces really big responses (several megabytes). To keep memory consumption tolerable, I would like to stream the response payload (using StAX or other approach).

    In optimal solution the payload would never at any point be stored in a DOM tree. XMLStreamWriter would just write directly to the output response.

    Can I do this with Spring-WS?

    I tried to do this with AbstractStaxStreamPayloadEndpoint but it seems that Spring-WS still stores the xml in DOM tree at some point:

    Code:
    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.OutOfMemoryError: Java heap space
    Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.util.Vector.<init>(Vector.java:111)
        at com.sun.org.apache.xerces.internal.dom.AttributeMap.setNamedItemNS(AttributeMap.java:193)
        at com.sun.org.apache.xerces.internal.dom.ElementImpl.setAttributeNS(ElementImpl.java:663)
        at com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl.setAttributeNS(ElementImpl.java:1241)
        at net.sf.saxon.dom.DOMEmitter.attribute(DOMEmitter.java:86)
        at net.sf.saxon.event.ProxyReceiver.attribute(ProxyReceiver.java:152)
        at net.sf.saxon.event.ReceivingContentHandler.startElement(ReceivingContentHandler.java:265)
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501)
        at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:179)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:377)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2740)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:647)
        at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
        at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:508)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807)
        at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
        at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107)
        at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
        at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
        at net.sf.saxon.event.Sender.sendSAXSource(Sender.java:270)
        at net.sf.saxon.event.Sender.send(Sender.java:144)
        at net.sf.saxon.IdentityTransformer.transform(IdentityTransformer.java:28)
        at org.springframework.xml.transform.TransformerObjectSupport.transform(TransformerObjectSupport.java:72)
        at org.springframework.ws.server.endpoint.AbstractStaxStreamPayloadEndpoint.access$100(AbstractStaxStreamPayloadEndpoint.java:47)
        at org.springframework.ws.server.endpoint.AbstractStaxStreamPayloadEndpoint$ResponseCreatingStreamWriter.close(AbstractStaxStreamPayloadEndpoint.java:154)
        at org.springframework.ws.server.endpoint.AbstractStaxStreamPayloadEndpoint.invoke(AbstractStaxStreamPayloadEndpoint.java:53)
        at org.springframework.ws.server.endpoint.adapter.MessageEndpointAdapter.invoke(MessageEndpointAdapter.java:41)
        at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:215)
        at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:162)
        at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:87)
        at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:57)
        at org.springframework.ws.transport.http.MessageDispatcherServlet.doService(MessageDispatcherServlet.java:197)
    My endpoint implementation looks like this:

    Code:
    public class StreamedEndpoint extends AbstractStaxStreamPayloadEndpoint {
    
    //...
    
        @Override
        protected void invokeInternal(XMLStreamReader streamReader,
                XMLStreamWriter streamWriter) throws Exception {
    
            String xml = readXmlFromDatabaseAsString();
            // parse xml string using XMLStreadReader and write to output XMLStreamWriter as we go along
            ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes());
            XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(stream);
            while (reader.hasNext()) {
                int eventType = reader.next();
                switch (eventType) {
                case XMLStreamConstants.START_ELEMENT:
                    streamWriter.writeStartElement(reader.getLocalName());
                    if (reader.getNamespaceURI() != null) {
                        String prefix = reader.getPrefix();
                        String uri = reader.getNamespaceURI();
                        streamWriter.writeNamespace(prefix, uri);
                    }
                    int attributeCount = reader.getAttributeCount();
                    for (int i = 0; i < attributeCount; ++i) {
                        String name = reader.getAttributeLocalName(i);
                        String value = reader.getAttributeValue(i);
                        streamWriter.writeAttribute(name, value);
                    }
                    break;
                case XMLStreamConstants.CHARACTERS:
                    streamWriter.writeCharacters(reader.getText());
                    break;
                case XMLStreamConstants.END_ELEMENT:
                    streamWriter.writeEndElement();
                    break;
                case XMLStreamConstants.END_DOCUMENT:
                    streamWriter.writeEndDocument();
                    break;
                default:
                    log.debug("Ignoring XML event " + eventType);
                }
            }
        }
    
    }
    The OUtOfMemoryError occurs after my endpoint has finished, from Spring-WS's internal processing.

  • #2
    *bump*

    Is it possible to create a web service which streams the XML message directly to the http response?

    Arjen, any comments?

    Comment


    • #3
      Originally posted by jimpo View Post
      *bump*

      Is it possible to create a web service which streams the XML message directly to the http response?
      In short: theoretically, it would be possible. In practice, it means that you can't use any interceptors.

      The basic response flow in Spring-WS is as follows: the endpoint creates the payload, we create a SOAP envelope around it. Then the EndpointInterceptors are invoked, who typically add SOAP headers to the response. FInally, the response is sent.

      Because the nature of a SOAP message, by the time we get to the body, we can't write the headers anymore. So we could have a SoapMessageFactory which writes directly to the transport outputstream, but it would disable any interceptors and logging.

      Comment


      • #4
        Originally posted by Arjen Poutsma View Post
        In short: theoretically, it would be possible. In practice, it means that you can't use any interceptors.

        The basic response flow in Spring-WS is as follows: the endpoint creates the payload, we create a SOAP envelope around it. Then the EndpointInterceptors are invoked, who typically add SOAP headers to the response. FInally, the response is sent.

        Because the nature of a SOAP message, by the time we get to the body, we can't write the headers anymore. So we could have a SoapMessageFactory which writes directly to the transport outputstream, but it would disable any interceptors and logging.
        Thanks.

        To make sure I read your answer correctly, do you mean that I can create a streaming web service right now, using Spring-WS as is, if I don't use any interceptors? Or, that it would be possible, if Spring-WS would be implemented a bit differently?

        I need to create a streaming web service. Basically I am just trying to gather whether I can use Spring-WS to do it, or do I need to use some other solution (xfire, some other?).

        You mention a different kind of SoapMessageFactory. Would it be feasible for us to implement a new implementation of SoapMessageFactory and use it for the streaming web services (and continue to use the "normal" SoapMessageFactory for the regular ones, which we also have). How easy or difficult would the implementation and configuration be?

        Comment


        • #5
          Originally posted by jimpo View Post
          Thanks.

          To make sure I read your answer correctly, do you mean that I can create a streaming web service right now, using Spring-WS as is, if I don't use any interceptors? Or, that it would be possible, if Spring-WS would be implemented a bit differently?

          I need to create a streaming web service. Basically I am just trying to gather whether I can use Spring-WS to do it, or do I need to use some other solution (xfire, some other?).

          You mention a different kind of SoapMessageFactory. Would it be feasible for us to implement a new implementation of SoapMessageFactory and use it for the streaming web services (and continue to use the "normal" SoapMessageFactory for the regular ones, which we also have). How easy or difficult would the implementation and configuration be?
          Spring-WS currently does not not streaming for outgoing messages. It does support streaming for incoming messages, using Axiom.

          It certainly is possible to create a SoapMessageFactory that streams outwards, but it's not easy. You need to get a reference to the transport output stream, using the TransportContextHolder. Then you basically have to throw UnsupportedOperationExceptions in every Soap* except for getPayloadResult().

          If I remember correctly, XFire does outward streaming, but faces the same problem we have: whenever you want to add headers to the envelope, it switches to a slower, DOM-based model.

          Comment


          • #6
            I had some high performance/throughput needs and was able to get spring-ws to avoid building the response into an axiom tree on the way out, while still being able to use interceptors (as long as they don't try to access the body contents). Spring-ws seems to perform/scale well nicely, but this did help increase the throughput a fair bit.

            This may or not work completely in a streaming fashion, I'm not sure.

            I was using the axiom message factory without payload caching, and I ended up using my own base endpoint class that implemented MessageEndpoint. Instead of my endpoint marshalling into the body.getPayloadResult() (which results in building an axiom tree for your response payload), I marshalled into a StreamResult feeding a byte array stream.

            After the endpoint did its work I inserted into the Axiom response tree a soap body payload using a newer feature of Axiom called an OMDataSource. OMDataSource lets you insert (inside OMSourcedElements) a single custom node for the soap body into the axiom response tree that doesn't get built into a full tree unless something like an interceptor tries to access the soap body contents.

            My custom OMDataSource was basically a wrapper around the byte array holding my raw response payload (whether it was marshalled from castor, stax, etc). I was using a newer cut of Axiom than the latest official 1.2.5 as it has some really nice enhancements in the OMDataSource area. This does require your custom data source hold onto something representing the payload until the whole soap message is ready to write to the transport, so true streaming might be tricky.

            I think one issue with axiom 1.2.5 (due to how its serializeAndConsume stuff works) is that you might need to get access to the transport output stream from your data source, but maybe the TransportContextHolder that Arjen suggested would help.

            If I get a chance, I'm hoping to see if I can find a clean way to do this automatically from within the spring-ws framework rather than using my own custom base endpoint class.

            Comment


            • #7
              Nice!

              Originally posted by jimcummings View Post
              If I get a chance, I'm hoping to see if I can find a clean way to do this automatically from within the spring-ws framework rather than using my own custom base endpoint class.
              I would be very interested in this!

              Comment


              • #8
                Originally posted by Arjen Poutsma View Post
                Nice!



                I would be very interested in this!
                Arjen - FYI - I was able to find a fairly clean way in AxiomSoapBody (when not doing payload caching) to prevent building an Axiom tree when sending back responses on the server-side as well as when sending requests on the client-side. This might help with some of the larger message perf posts, as well as general perf of medium-sized messages.

                I just need to do a little perf testing and finish the unit tests. I should be able to post the patch in a jira issue within the week.

                Comment


                • #9
                  I believe this jira addresses the discussion here.

                  jira.
                  FILLER, REMOVE BECAUSE THE FORUM WONT LET ME POST A URL
                  springframework.org/browse/SWS-352?page=com.atlassian.jira.plugin.system.issuetab panels:all-tabpanel

                  has anyone done work they'd be willing to share?

                  Comment


                  • #10
                    Has this been resolved yet? I see that SWS-302 is closed, but, SWS-352 is still unresolved. (sorry, it won't let me post URLs)

                    I'm trying to write a web service that returns a large response (several megabytes). I'm using STaX (AbstractStaxStreamPayloadEndpoint) and Axiom. I have to leave payload caching enabled to process the security headers on the request.

                    When I make a request using SoapUI (in Eclipse) I get read timeouts because the web service appears to be building the response in memory instead of streaming it out--at least that's what it appears to be doing. Are there any examples of a streaming Spring web service out there? Should I be looking at other alternatives? The service I'm coding needed to be in production yesterday (of course).

                    I'm only concerned with streaming the response. The request is a few kilobytes. The response is several MBs.

                    Comment


                    • #11
                      Short term solution is to adjust your JVM -Xmx to something like 1024 whould get you through about 100MB in message size (bloats to up to 10x the message size in memory, ) hope that helps.

                      Comment


                      • #12
                        Originally posted by jrabbitb View Post
                        Short term solution is to adjust your JVM -Xmx to something like 1024 whould get you through about 100MB in message size (bloats to up to 10x the message size in memory, ) hope that helps.
                        I'm not running into memory issues, yet. The bigger issue is that the client may timeout before a response is received.

                        Comment


                        • #13
                          Originally posted by bkim View Post


                          When I make a request using SoapUI (in Eclipse) I get read timeouts because the web service appears to be building the response in memory instead of streaming it out--at least that's what it appears to be doing. Are there any examples of a streaming Spring web service out there? Should I be looking at other alternatives? The service I'm coding needed to be in production yesterday (of course).
                          Sorry, i didn't read your message closely enough the first time.

                          the problem is that yes, StAX will let you stream read your xml, but when you are building the SOAP message it must load the entire xml tree into memory. so sadly the answer right now is, no, there is no fix for this to force it to stream out but you should be able to change your connection timeout in code, i'm just not sure where off the top of my head, sorry.

                          Comment


                          • #14
                            Originally posted by jrabbitb View Post
                            the problem is that yes, StAX will let you stream read your xml, but when you are building the SOAP message it must load the entire xml tree into memory. so sadly the answer right now is, no, there is no fix for this to force it to stream out but you should be able to change your connection timeout in code, i'm just not sure where off the top of my head, sorry.
                            I figured as much. This is what I did to increase the timeout:

                            Code:
                            	<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
                            		<property name="messageSender">
                            			<bean class="org.springframework.ws.transport.http.CommonsHttpMessageSender">
                            				<property name="acceptGzipEncoding" value="true"/>
                            				<property name="readTimeout" value="600000"/>
                            			</bean>
                            		</property>
                            	</bean>
                            Does anyone know if other web service frameworks have the same streaming limitation (e.g. Axis, CXF...)?

                            Comment


                            • #15
                              i know axis has the limitation. AXIOM is the AXIs Object Model. And unless something has changed you will find the same problem everywhere. You could manually break the message up and send several smaller chunks, this could help but is a pain to code. another option going back to the -Xmx idea is also to set the -Xms to a higher value. the default is 64MB, when the jvm fills that it will request more memory and expand the heap up to the -xmx, if you set the xms higher then 64mb then the jvm will not take as much time building the message and thus you may be able to avoid your timeout issue.

                              problems on both solutions are:
                              if you adjust the jvm you are not going to really fix the timeout issue just help avoid it.
                              the problem with sending chunks is increased coupling between sender and receiver.

                              Comment

                              Working...
                              X