I've made a couple line change to Apache SOAP that improves latency 10x in the best case. The code is attached as I'm not a contributor to Apache SOAP or AXIS.
/* * The Apache Software License, Version 1.1 * * * Copyright (c) 2000 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "SOAP" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation and was * originally based on software copyright (c) 2000, International * Business Machines, Inc., http://www.apache.org. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.soap.util.net; import java.net.*; import java.io.*; import java.util.*; import org.apache.soap.*; import org.apache.soap.rpc.*; import org.apache.soap.transport.*; import org.apache.soap.util.mime.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; import java.lang.reflect.*; /** * A bunch of utility stuff for doing HTTP things. * <p> * 2000/07/30 W. Cloetens added Multipart Mime support * * @author Sanjiva Weerawarana ([EMAIL PROTECTED]) * @author Matthew J. Duftler ([EMAIL PROTECTED]) * @author Wouter Cloetens ([EMAIL PROTECTED]) * @author Scott Nichol ([EMAIL PROTECTED]) */ public class HTTPUtils { private static final String HTTP_VERSION = "1.0"; private static final int HTTP_DEFAULT_PORT = 80; private static final int HTTPS_DEFAULT_PORT = 443; public static final int DEFAULT_OUTPUT_BUFFER_SIZE = 512; /** * This method either creates a socket or calls SSLUtils to * create an SSLSocket. It uses reflection to avoid a compile time * dependency on SSL. * * @author Chris Nelson */ private static Socket buildSocket(URL url, int targetPort, String httpProxyHost, int httpProxyPort) throws Exception { Socket s = null; String host = null; int port = targetPort; host = url.getHost(); if (url.getProtocol().equalsIgnoreCase("HTTPS")) { // Using reflection to avoid compile time dependencies Class SSLUtilsClass = Class.forName("org.apache.soap.util.net.SSLUtils"); Class[] paramTypes = new Class[] {String.class, int.class, String.class, int.class}; Method buildSSLSocket = SSLUtilsClass.getMethod( "buildSSLSocket", paramTypes); Object[] params = new Object[] {host, new Integer(port), httpProxyHost, new Integer(httpProxyPort)}; s = (Socket)buildSSLSocket.invoke(null, params); } else { if (httpProxyHost != null) { host = httpProxyHost; port = httpProxyPort; } s = new Socket(host, port); } // DAN 03-08-2002 -- On the client side, set the no-delay option // It's unclear whether: // (a) The browser security rules allows this in an applet // (b) This will improve performance for SOAP over an SSL connection // (c) This will actually call setsockopt() on the underlying // connection if (s != null) s.setTcpNoDelay (true); return s; } /** * Utility function to determine port number from URL object. * * @param url URL object from which to determine port number * @return port number */ private static int getPort(URL url) throws IOException { int port = url.getPort(); if (port < 0) // No port given, use HTTP or HTTPS default if (url.getProtocol().equalsIgnoreCase("HTTPS")) port = HTTPS_DEFAULT_PORT; else port = HTTP_DEFAULT_PORT; return port; } /** * POST something to the given URL. The headers are put in as * HTTP headers, the content length is calculated and the content * byte array is sent as the POST content. * * @param url the url to post to * @param request the message * @param timeout the amount of time, in ms, to block on reading data * @param httpProxyHost the HTTP proxy host or null if no proxy * @param httpProxyPort the HTTP proxy port, if the proxy host is not null * @return the response message */ public static TransportMessage post(URL url, TransportMessage request, int timeout, String httpProxyHost, int httpProxyPort) throws IllegalArgumentException, IOException, SOAPException { return post(url, request, timeout, httpProxyHost, httpProxyPort, DEFAULT_OUTPUT_BUFFER_SIZE); } /** * POST something to the given URL. The headers are put in as * HTTP headers, the content length is calculated and the content * byte array is sent as the POST content. * * @param url the url to post to * @param request the message * @param timeout the amount of time, in ms, to block on reading data * @param httpProxyHost the HTTP proxy host or null if no proxy * @param httpProxyPort the HTTP proxy port, if the proxy host is not null * @param outputBufferSize the size of the output buffer on the HTTP stream * @return the response message */ public static TransportMessage post(URL url, TransportMessage request, int timeout, String httpProxyHost, int httpProxyPort, int outputBufferSize) throws IllegalArgumentException, IOException, SOAPException { /* Open the connection */ OutputStream outStream = null; InputStream inStream = null; BufferedReader in = null; int port; Socket s; try { port = getPort(url); s = buildSocket(url, port, httpProxyHost, httpProxyPort); if (url.getProtocol().equalsIgnoreCase("HTTPS")) { // Ignore proxy from now on. Buildsocket takes handles it httpProxyHost = null; } if (timeout > 0) // Should be redundant but not every JVM likes this s.setSoTimeout(timeout); outStream = s.getOutputStream (); inStream = s.getInputStream (); } catch (Exception e) { throw new IllegalArgumentException("Error opening socket: " + e.getMessage ()); } /* Compute the Request URI */ String URI = (httpProxyHost == null ? url.getFile() : url.toString()); if (URI.length() == 0) URI = "/"; /* Construct the HTTP header. */ StringBuffer headerbuf = new StringBuffer(); headerbuf.append(Constants.HEADER_POST).append(' ').append(URI) .append(" HTTP/").append(HTTP_VERSION).append("\r\n") .append(Constants.HEADER_HOST).append(": ").append(url.getHost()) // .append(':').append(port) .append("\r\n") .append(Constants.HEADER_CONTENT_TYPE).append(": ") .append(request.getContentType()).append("\r\n") .append(Constants.HEADER_CONTENT_LENGTH).append(": ") .append(request.getContentLength()).append("\r\n"); for (Enumeration e = request.getHeaderNames(); e.hasMoreElements(); ) { Object key = e.nextElement(); headerbuf.append(key).append(": ") .append(request.getHeader((String)key)).append("\r\n"); } headerbuf.append("\r\n"); /* Send the request. */ BufferedOutputStream bOutStream = new BufferedOutputStream(outStream, outputBufferSize); bOutStream.write( headerbuf.toString().getBytes(Constants.HEADERVAL_DEFAULT_CHARSET)); request.writeTo(bOutStream); //bOutStream.write('\r'); bOutStream.write('\n'); //bOutStream.write('\r'); bOutStream.write('\n'); bOutStream.flush(); outStream.flush(); BufferedInputStream bInStream = new BufferedInputStream(inStream); /* Read the response status line. */ int statusCode = 0; String statusString = null; StringBuffer linebuf = new StringBuffer(); int b = 0; while (b != '\n' && b != -1) { b = bInStream.read(); if (b != '\n' && b != '\r' && b != -1) linebuf.append((char)b); } String line = linebuf.toString(); try { StringTokenizer st = new StringTokenizer(line); st.nextToken(); // ignore version part statusCode = Integer.parseInt (st.nextToken()); StringBuffer sb = new StringBuffer(); while (st.hasMoreTokens()) { sb.append (st.nextToken()); if (st.hasMoreTokens()) { sb.append(" "); } } statusString = sb.toString(); } catch (Exception e) { throw new IllegalArgumentException( "Error parsing HTTP status line \"" + line + "\": " + e); } /* Read the entire response (following the status line) * into a byte array. */ ByteArrayDataSource ds = new ByteArrayDataSource(bInStream, Constants.HEADERVAL_DEFAULT_CHARSET); /* Extract the headers, content type and content length. */ byte[] bytes = ds.toByteArray(); Hashtable respHeaders = new Hashtable(); int respContentLength = -1; String respContentType = null; StringBuffer namebuf = new StringBuffer(); StringBuffer valuebuf = new StringBuffer(); boolean parsingName = true; int offset; for (offset = 0; offset < bytes.length; offset++) { if (bytes[offset] == '\n') { if (namebuf.length() == 0) break; String name = namebuf.toString(); // Remove trailing ; to prevent ContextType from throwing exception int valueLen = valuebuf.length(); if (valueLen > 0 && valuebuf.charAt(valueLen - 1) == ';') { valuebuf.deleteCharAt(valueLen - 1); } String value = valuebuf.toString(); if (name.equalsIgnoreCase(Constants.HEADER_CONTENT_LENGTH)) respContentLength = Integer.parseInt(value); else if (name.equalsIgnoreCase(Constants.HEADER_CONTENT_TYPE)) respContentType = value; else respHeaders.put(name, value); namebuf = new StringBuffer(); valuebuf = new StringBuffer(); parsingName = true; } else if (bytes[offset] != '\r') { if (parsingName) { if (bytes[offset] == ':') { parsingName = false; if ((offset != bytes.length-1) && bytes[offset+1] == ' ') offset++; } else namebuf.append((char)bytes[offset]); } else valuebuf.append((char)bytes[offset]); } } InputStream is = ds.getInputStream(); is.skip(offset + 1); if (respContentLength < 0) respContentLength = ds.getSize() - offset - 1; /* Construct the response object. */ SOAPContext ctx; TransportMessage response; try { // Create response SOAPContext. ctx = new SOAPContext(); // Read content. response = new TransportMessage(is, respContentLength, respContentType, ctx, respHeaders); // Extract envelope and SOAPContext response.read(); } catch (MessagingException me) { throw new IllegalArgumentException("Error parsing response: " + me); } /* All done here! */ bOutStream.close(); outStream.close(); bInStream.close(); inStream.close(); s.close(); return response; } }