Timothy Peierls <[EMAIL PROTECTED]> writes:

> Daniel Rall wrote:
> >> That means the same data is buffered in memory (in various 
>>> forms) a minimum of 4 times (!!). If the response contains 
>>> large quantities of data, imagine the repercussions...
>
> I've observed significant overhead when returning large result
> objects. It used to be worse, before the intermediate values were
> cleared in XmlRpc.Worker, but it's still wasteful.
>
> So far, the response to this has centered on how the possibilities for
> streaming are limited by the need to produce the response body in
> memory before writing the Content-length header. However, there are
> other ways things could be improved.

Tim, thanks a lot for your comments.

> 1. It might be reasonable to perform two passes over the result
> object, one to compute the content length, and then one to stream it
> out.

The value of this change would depend heavily on how long it takes to
create the response, and just doesn't seem very elegant (though
neither does a frozen XML-RPC spec, bleh).

> 2. In any event, why not have XmlWriter write directly to an
> OutputStreamWriter that wraps a ByteArrayOutputStream (maybe itself
> wrapped in a BufferedWriter)? This would eliminate the need for a
> StringBuffer, which is one of the problem areas that Dan is worried
> about.

I was thinking along the same lines, but your description really
helped crystalize my thoughts.  I made XmlWriter sub-class the JDK's
OutputStreamWriter (for simplicity), and moved writeObject() into it.
I am about to commit to CVS, but have attached the patch to the end of
this message for comment and review.  As this is a fairly extensive
change, it would be great to get some eyes on it.

> 3. And, at the risk of being a one-note Johnny, I'll point out that
> were we to use List and Map instead of Vector and Hashtable, server
> side code that, for example, currently constructs a Vector in memory
> just to hold the results of iterating over some internal data
> structure might instead be able to use a custom List implementation
> that would effectively stream those results directly from the List's
> Iterator without having to form an intermediate array or Vector.

Tim, I would love to see some code implementing this idea!


All, please comment on these changes:

Index: src/java/org/apache/xmlrpc/XmlRpc.java
===================================================================
RCS file: /home/cvs/xml-rpc/src/java/org/apache/xmlrpc/XmlRpc.java,v
retrieving revision 1.16
diff -u -r1.16 XmlRpc.java
--- src/java/org/apache/xmlrpc/XmlRpc.java      18 Feb 2002 17:06:15 -0000      1.16
+++ src/java/org/apache/xmlrpc/XmlRpc.java      18 Feb 2002 22:44:16 -0000
@@ -72,6 +72,7 @@
  * @see XmlRpcClient
  *
  * @author <a href="mailto:[EMAIL PROTECTED]";>Hannes Wallnoefer</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]";>Daniel Rall</a>
  */
 public abstract class XmlRpc 
     extends HandlerBase
@@ -331,89 +332,6 @@
     }
 
     /**
-      * Writes the XML representation of a supported Java object to
-      * the XML writer.
-      */
-    void writeObject (Object what, XmlWriter writer)
-    {
-        writer.startElement ("value");
-        if (what == null)
-        {
-            throw new RuntimeException ("null value not supported by XML-RPC");
-        }
-        else if (what instanceof String)
-        {
-            writer.chardata (what.toString ());
-        }
-        else if (what instanceof Integer)
-        {
-            writer.startElement ("int");
-            writer.write (what.toString ());
-            writer.endElement ("int");
-        }
-        else if (what instanceof Boolean)
-        {
-            writer.startElement ("boolean");
-            writer.write (((Boolean) what).booleanValue () ? "1" : "0");
-            writer.endElement ("boolean");
-        }
-        else if (what instanceof Double || what instanceof Float)
-        {
-            writer.startElement ("double");
-            writer.write (what.toString ());
-            writer.endElement ("double");
-        }
-        else if (what instanceof Date)
-        {
-            writer.startElement ("dateTime.iso8601");
-            Date d = (Date) what;
-            writer.write (dateformat.format (d));
-            writer.endElement ("dateTime.iso8601");
-        }
-        else if (what instanceof byte[])
-        {
-            writer.startElement ("base64");
-            writer.write (Base64.encode ((byte[]) what));
-            writer.endElement ("base64");
-        }
-        else if (what instanceof Vector)
-        {
-            writer.startElement ("array");
-            writer.startElement ("data");
-            Vector v = (Vector) what;
-            int l2 = v.size ();
-            for (int i2 = 0; i2 < l2; i2++)
-                writeObject (v.elementAt (i2), writer);
-            writer.endElement ("data");
-            writer.endElement ("array");
-        }
-        else if (what instanceof Hashtable)
-        {
-            writer.startElement ("struct");
-            Hashtable h = (Hashtable) what;
-            for (Enumeration e = h.keys (); e.hasMoreElements ();)
-            {
-                String nextkey = (String) e.nextElement ();
-                Object nextval = h.get (nextkey);
-                writer.startElement ("member");
-                writer.startElement ("name");
-                writer.write (nextkey);
-                writer.endElement ("name");
-                writeObject (nextval, writer);
-                writer.endElement ("member");
-            }
-            writer.endElement ("struct");
-        }
-        else
-        {
-            throw new RuntimeException ("unsupported Java type: " +
-                    what.getClass ());
-        }
-        writer.endElement ("value");
-    }
-
-
-    /**
       *  This method is called when a root level object has been parsed.
       */
     abstract void objectParsed (Object what);
@@ -700,9 +618,14 @@
 
 
     /**
-     * A quick and dirty XML writer.
+     * A quick and dirty XML writer.  If you feed it a
+     * <code>ByteArrayInputStream</code>, it may be necessary to call
+     * <code>writer.flush()</code> before calling
+     * <code>buffer.toByteArray()</code> to get the data written to
+     * your byte buffer.
      */
     class XmlWriter
+        extends OutputStreamWriter
     {
         protected static final String PROLOG_START =
             "<?xml version=\"1.0\" encoding=\"";
@@ -713,55 +636,137 @@
         protected static final String GREATER_THAN_ENTITY = "&gt;";
         protected static final String AMPERSAND_ENTITY = "&amp;";
 
-        /**
-         * The buffer to write to.
-         */
-        StringBuffer buf;
-
-        /**
-         * The encoding to use.
-         */
-        String enc;
-
-        public XmlWriter (StringBuffer buf)
+        public XmlWriter (OutputStream out)
+            throws UnsupportedEncodingException, IOException
         {
             // The default encoding used for XML-RPC is ISO-8859-1.
-            this (buf, encoding);
+            this (out, encoding);
         }
 
-        public XmlWriter (StringBuffer buf, String enc)
+        public XmlWriter (OutputStream out, String enc)
+            throws UnsupportedEncodingException, IOException
         {
-            this.buf = buf;
-            this.enc = enc;
+            super(out, enc);
 
             // Add the XML prolog (which includes the encoding)
-            buf.append (PROLOG_START);
-            buf.append (encodings.getProperty (enc, enc));
-            buf.append (PROLOG_END);
+            write(PROLOG_START);
+            write(encodings.getProperty(enc, enc));
+            write(PROLOG_END);
         }
 
-        public void startElement (String elem)
+        /**
+         * Writes the XML representation of a supported Java object
+         * type.
+         *
+         * @param obj The <code>Object</code> to write.
+         */
+        public void writeObject (Object obj)
+            throws IOException
         {
-            buf.append ('<');
-            buf.append (elem);
-            buf.append ('>');
+            startElement ("value");
+            if (obj == null)
+            {
+                throw new RuntimeException("null value not supported by XML-RPC");
+            }
+            else if (obj instanceof String)
+            {
+                chardata(obj.toString());
+            }
+            else if (obj instanceof Integer)
+            {
+                startElement("int");
+                write(obj.toString());
+                endElement("int");
+            }
+            else if (obj instanceof Boolean)
+            {
+                startElement("boolean");
+                write(((Boolean) obj).booleanValue() ? "1" : "0");
+                endElement("boolean");
+            }
+            else if (obj instanceof Double || obj instanceof Float)
+            {
+                startElement("double");
+                write(obj.toString());
+                endElement("double");
+            }
+            else if (obj instanceof Date)
+            {
+                startElement("dateTime.iso8601");
+                Date d = (Date) obj;
+                write(dateformat.format(d));
+                endElement("dateTime.iso8601");
+            }
+            else if (obj instanceof byte[])
+            {
+                startElement("base64");
+                write(Base64.encode((byte[]) obj));
+                endElement("base64");
+            }
+            else if (obj instanceof Vector)
+            {
+                startElement("array");
+                startElement("data");
+                Vector array = (Vector) obj;
+                int size = array.size();
+                for (int i = 0; i < size; i++)
+                {
+                    writeObject(array.elementAt(i));
+                }
+                endElement("data");
+                endElement("array");
+            }
+            else if (obj instanceof Hashtable)
+            {
+                startElement("struct");
+                Hashtable struct = (Hashtable) obj;
+                for (Enumeration e = struct.keys(); e.hasMoreElements(); )
+                {
+                    String nextkey = (String) e.nextElement();
+                    Object nextval = struct.get(nextkey);
+                    startElement("member");
+                    startElement("name");
+                    write(nextkey);
+                    endElement("name");
+                    writeObject(nextval);
+                    endElement("member");
+                }
+                endElement("struct");
+            }
+            else
+            {
+                throw new RuntimeException("unsupported Java type: " +
+                                            obj.getClass());
+            }
+            endElement("value");
+        }
+
+        protected void startElement (String elem)
+            throws IOException
+        {
+            write('<');
+            write(elem);
+            write('>');
         }
 
-        public void endElement (String elem)
+        protected void endElement (String elem)
+            throws IOException
         {
-            buf.append (CLOSING_TAG_START);
-            buf.append (elem);
-            buf.append ('>');
+            write(CLOSING_TAG_START);
+            write(elem);
+            write('>');
         }
 
-        public void emptyElement (String elem)
+        protected void emptyElement (String elem)
+            throws IOException
         {
-            buf.append ('<');
-            buf.append (elem);
-            buf.append (SINGLE_TAG_END);
+            write('<');
+            write(elem);
+            write(SINGLE_TAG_END);
         }
 
-        public void chardata (String text)
+        protected void chardata (String text)
+            throws IOException
         {
             int l = text.length ();
             for (int i = 0; i < l; i++)
@@ -769,39 +774,19 @@
                 char c = text.charAt (i);
                 switch (c)
                 {
-                case '<' :
-                    buf.append (LESS_THAN_ENTITY);
+                case '<':
+                    write(LESS_THAN_ENTITY);
                     break;
-                case '>' :
-                    buf.append (GREATER_THAN_ENTITY);
+                case '>':
+                    write(GREATER_THAN_ENTITY);
                     break;
-                case '&' :
-                    buf.append (AMPERSAND_ENTITY);
+                case '&':
+                    write(AMPERSAND_ENTITY);
                     break;
-                default :
-                    buf.append (c);
+                default:
+                    write(c);
                 }
             }
-        }
-
-        public void write (char[] text)
-        {
-            buf.append (text);
-        }
-
-        public void write (String text)
-        {
-            buf.append (text);
-        }
-
-        public String toString ()
-        {
-            return buf.toString ();
-        }
-
-        public byte[] getBytes () throws UnsupportedEncodingException
-        {
-            return buf.toString ().getBytes (enc);
         }
     }
 }
Index: src/java/org/apache/xmlrpc/XmlRpcClient.java
===================================================================
RCS file: /home/cvs/xml-rpc/src/java/org/apache/xmlrpc/XmlRpcClient.java,v
retrieving revision 1.5
diff -u -r1.5 XmlRpcClient.java
--- src/java/org/apache/xmlrpc/XmlRpcClient.java        18 Feb 2002 17:06:15 -0000     
 1.5
+++ src/java/org/apache/xmlrpc/XmlRpcClient.java        18 Feb 2002 22:44:17 -0000
@@ -254,7 +254,11 @@
     {
         boolean fault;
         Object result = null;
-        StringBuffer strbuf;
+
+        /**
+         * The output buffer used in creating a request.
+         */
+        ByteArrayOutputStream buffer;
 
         CallData call;
 
@@ -298,12 +302,15 @@
             catch (Exception x)
             {
                 if (callback != null)
+                {
                     try
                     {
                         callback.handleError (x, url, method);
                     }
                     catch (Exception ignore)
-                    {}
+                    {
+                    }
+                }
             }
         }
 
@@ -323,14 +330,19 @@
             {
                 ByteArrayOutputStream bout = new ByteArrayOutputStream ();
 
-                if (strbuf == null)
-                    strbuf = new StringBuffer ();
+                if (buffer == null)
+                {
+                    buffer = new ByteArrayOutputStream();
+                }
                 else
-                    strbuf.setLength (0);
+                {
+                    buffer.reset();
+                }
 
-                XmlWriter writer = new XmlWriter (strbuf);
+                XmlWriter writer = new XmlWriter (buffer);
                 writeRequest (writer, method, params);
-                byte[] request = writer.getBytes();
+                writer.flush();
+                byte[] request = buffer.toByteArray();
 
                 URLConnection con = url.openConnection ();
                 con.setDoInput (true);
@@ -407,7 +419,7 @@
             for (int i = 0; i < l; i++)
             {
                 writer.startElement ("param");
-                writeObject (params.elementAt (i), writer);
+                writer.writeObject (params.elementAt (i));
                 writer.endElement ("param");
             }
             writer.endElement ("params");
Index: src/java/org/apache/xmlrpc/XmlRpcClientLite.java
===================================================================
RCS file: /home/cvs/xml-rpc/src/java/org/apache/xmlrpc/XmlRpcClientLite.java,v
retrieving revision 1.2
diff -u -r1.2 XmlRpcClientLite.java
--- src/java/org/apache/xmlrpc/XmlRpcClientLite.java    18 Feb 2002 17:06:15 -0000     
 1.2
+++ src/java/org/apache/xmlrpc/XmlRpcClientLite.java    18 Feb 2002 22:44:18 -0000
@@ -126,7 +126,6 @@
 
     class LiteWorker extends Worker implements Runnable
     {
-
         HttpClient client = null;
 
         public LiteWorker ()
@@ -134,21 +133,25 @@
             super ();
         }
 
-
-        Object execute (String method,
-                Vector params) throws XmlRpcException, IOException
+        Object execute (String method, Vector params)
+            throws XmlRpcException, IOException
         {
             long now = System.currentTimeMillis ();
             fault = false;
             try
             {
-                if (strbuf == null)
-                    strbuf = new StringBuffer ();
+                if (buffer == null)
+                {
+                    buffer = new ByteArrayOutputStream();
+                }
                 else
-                    strbuf.setLength (0);
-                XmlWriter writer = new XmlWriter (strbuf);
+                {
+                    buffer.reset();
+                }
+                XmlWriter writer = new XmlWriter (buffer);
                 writeRequest (writer, method, params);
-                byte[] request = writer.getBytes();
+                writer.flush();
+                byte[] request = buffer.toByteArray();
 
                 // and send it to the server
                 if (client == null)
Index: src/java/org/apache/xmlrpc/XmlRpcServer.java
===================================================================
RCS file: /home/cvs/xml-rpc/src/java/org/apache/xmlrpc/XmlRpcServer.java,v
retrieving revision 1.10
diff -u -r1.10 XmlRpcServer.java
--- src/java/org/apache/xmlrpc/XmlRpcServer.java        18 Feb 2002 17:06:15 -0000     
 1.10
+++ src/java/org/apache/xmlrpc/XmlRpcServer.java        18 Feb 2002 22:44:19 -0000
@@ -66,6 +66,7 @@
  * HTTP listener, use the WebServer class instead.
  *
  * @author <a href="mailto:[EMAIL PROTECTED]";>Hannes Wallnoefer</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]";>Daniel Rall</a>
  */
 public class XmlRpcServer
 {
@@ -150,8 +151,9 @@
      */
     class Worker extends XmlRpc
     {
-        Vector inParams;
-        StringBuffer strbuf;
+        private Vector inParams;
+        private ByteArrayOutputStream buffer;
+        private XmlWriter writer;
 
         /**
          * Creates a new instance.
@@ -159,7 +161,7 @@
         protected Worker()
         {
             inParams = new Vector();
-            strbuf = new StringBuffer();
+            buffer = new ByteArrayOutputStream();
         }
 
         /**
@@ -175,7 +177,7 @@
             finally
             {
                 // Release most of our resources
-                strbuf.setLength (0);
+                buffer.reset();
                 inParams.removeAllElements();
             }
         }
@@ -227,17 +229,22 @@
 
                 Object outParam;
                 if (handler instanceof AuthenticatedXmlRpcHandler)
+                {
                     outParam = ((AuthenticatedXmlRpcHandler) handler).
                             execute (methodName, inParams, user, password);
+                }
                 else
+                {
                     outParam = ((XmlRpcHandler) handler).execute (
                             methodName, inParams);
+                }
                 if (debug)
                     System.err.println ("outparam = "+outParam);
 
-                XmlWriter writer = new XmlWriter (strbuf);
+                writer = new XmlWriter(buffer);
                 writeResponse (outParam, writer);
-                result = writer.getBytes ();
+                writer.flush();
+                result = buffer.toByteArray();
             }
             catch (Exception x)
             {
@@ -249,32 +256,69 @@
                 // It is possible that something is in the buffer
                 // if there were an exception during the writeResponse()
                 // call above.
-                strbuf.setLength(0);
+                buffer.reset();
+
+                writer = null;
+                try
+                {
+                    writer = new XmlWriter(buffer);
+                }
+                catch (UnsupportedEncodingException encx)
+                {
+                    System.err.println("XmlRpcServer attempted to use " +
+                                       "unsupported encoding: " + encx);
+                    // NOTE: If we weren't already using the default
+                    // encoding, we could try it here.
+                }
+                catch (IOException iox)
+                {
+                    System.err.println("XmlRpcServer experienced I/O error " +
+                                       "writing error response: " + iox);
+                }
 
-                XmlWriter writer = new XmlWriter (strbuf);
                 String message = x.toString ();
-                // check if XmlRpcException was thrown so we can get an error code
+                // Retrieve XmlRpcException error code (if possible).
                 int code = x instanceof XmlRpcException ?
                         ((XmlRpcException) x).code : 0;
                 try
                 {
                     writeError (code, message, writer);
+                    writer.flush();
                 }
-                catch (XmlRpcException e)
+                catch (Exception e)
                 {
                     // Unlikely to occur, as we just sent a struct
                     // with an int and a string.
                     System.err.println("Unable to send error response to " +
                                        "client: " + e);
                 }
-                try
+
+                // If we were able to create a XmlWriter, we should
+                // have a response.
+                if (writer != null)
                 {
-                    result = writer.getBytes ();
+                    result = buffer.toByteArray();
                 }
-                catch (UnsupportedEncodingException encx)
+                else
+                {
+                    result = "".getBytes();
+                }
+            }
+            finally
+            {
+                if (writer != null)
                 {
-                    System.err.println ("XmlRpcServer.execute: "+encx);
-                    result = writer.toString().getBytes();
+                    try
+                    {
+                        writer.close();
+                    }
+                    catch (IOException iox)
+                    {
+                        // This is non-fatal, but worth logging a
+                        // warning for.
+                        System.err.println("Exception closing output stream: " +
+                                           iox);
+                    }
                 }
             }
             if (debug)
@@ -284,8 +328,9 @@
         }
 
         /**
-          * Called when an object to be added to the argument list has been parsed.
-          */
+         * Called when an object to be added to the argument list has
+         * been parsed.
+         */
         void objectParsed (Object what)
         {
             inParams.addElement (what);
@@ -294,14 +339,14 @@
         /**
           * Writes an XML-RPC response to the XML writer.
           */
-        void writeResponse (Object param,
-                XmlWriter writer) throws XmlRpcException
+        void writeResponse (Object param, XmlWriter writer)
+            throws XmlRpcException, IOException
         {
             writer.startElement ("methodResponse");
             // if (param == null) param = ""; // workaround for Frontier bug
             writer.startElement ("params");
             writer.startElement ("param");
-            writeObject (param, writer);
+            writer.writeObject (param);
             writer.endElement ("param");
             writer.endElement ("params");
             writer.endElement ("methodResponse");
@@ -310,8 +355,8 @@
         /**
          * Writes an XML-RPC error response to the XML writer.
          */
-        void writeError (int code, String message,
-                XmlWriter writer) throws XmlRpcException
+        void writeError (int code, String message, XmlWriter writer)
+            throws XmlRpcException, IOException
         {
             // System.err.println ("error: "+message);
             Hashtable h = new Hashtable ();
@@ -319,7 +364,7 @@
             h.put ("faultString", message);
             writer.startElement ("methodResponse");
             writer.startElement ("fault");
-            writeObject (h, writer);
+            writer.writeObject (h);
             writer.endElement ("fault");
             writer.endElement ("methodResponse");
         }

Reply via email to