snichol 2002/11/12 06:15:38 Modified: java/src/org/apache/soap/util/net HTTPUtils.java java/src/org/apache/soap/transport TransportMessage.java java/src/org/apache/soap/transport/http SOAPHTTPConnection.java Log: Reduce the number of times a response is copied in part or in whole. Improve error reporting during response parsing. Support services that shutdown the write half of the socket rather than provide a Content-Length header. Add getEnvelope to SOAPHTTPConnection. (The method will also be added to SOAPTransport soon.) Revision Changes Path 1.36 +104 -82 xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java Index: HTTPUtils.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java,v retrieving revision 1.35 retrieving revision 1.36 diff -u -r1.35 -r1.36 --- HTTPUtils.java 18 Oct 2002 20:30:54 -0000 1.35 +++ HTTPUtils.java 12 Nov 2002 14:15:38 -0000 1.36 @@ -57,21 +57,30 @@ package org.apache.soap.util.net; -import java.io.*; -import java.lang.reflect.*; -import java.net.*; -import java.util.*; - -import javax.mail.*; -import javax.mail.internet.*; -import javax.activation.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.StringTokenizer; -import org.apache.soap.*; +import javax.mail.MessagingException; + +import org.apache.soap.Constants; +import org.apache.soap.SOAPException; import org.apache.soap.encoding.soapenc.Base64; -import org.apache.soap.rpc.*; -import org.apache.soap.transport.*; +import org.apache.soap.rpc.SOAPContext; +import org.apache.soap.transport.TransportMessage; import org.apache.soap.util.MutableBoolean; -import org.apache.soap.util.mime.*; /** * A bunch of utility stuff for doing HTTP things. @@ -91,6 +100,7 @@ private static final String HTTP_VERSION = "1.0"; private static final int HTTP_DEFAULT_PORT = 80; private static final int HTTPS_DEFAULT_PORT = 443; + private static final String ISO_8859_1 = "8859_1"; public static final int DEFAULT_OUTPUT_BUFFER_SIZE = 8 * 1024; @@ -100,7 +110,7 @@ public static String encodeAuth(String userName, String password) throws SOAPException { try { - return Base64.encode((userName + ":" + password).getBytes("8859_1")); + return Base64.encode((userName + ":" + password).getBytes(ISO_8859_1)); } catch (UnsupportedEncodingException e) { throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); } @@ -482,65 +492,93 @@ bOutStream.flush(); BufferedInputStream bInStream = new BufferedInputStream(inStream); + byte[] linebuf = new byte[1024]; + int count = 0; + int b; + /* Read the response status line. */ + String versionString = null; int statusCode = 0; String statusString = null; - StringBuffer linebuf = new StringBuffer(128); - 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(128); - while (st.hasMoreTokens()) { - sb.append (st.nextToken()); - if (st.hasMoreTokens()) { - sb.append(" "); + int versionEnd = -1; + int codeStart = -1; + int codeEnd = -1; + int stringStart = -1; + + for (count = 0, b = bInStream.read(); b != '\n' && b != -1; b = bInStream.read()) { + if (b != '\r') { + if (b == ' ') { + if (versionEnd == -1) { + versionEnd = count; + } else if (codeStart != -1 && codeEnd == -1) { + codeEnd = count; + } + } else { + if (versionEnd != -1 && codeStart == -1) { + codeStart = count; + } else if (codeEnd != -1 && stringStart == -1) { + stringStart = count; + } + } + if (count >= linebuf.length) { + byte[] newbuf = new byte[linebuf.length * 2]; + System.arraycopy(linebuf, 0, newbuf, 0, linebuf.length); + linebuf = newbuf; + } + linebuf[count++] = (byte) b; } } - statusString = sb.toString(); - } - catch (Exception e) { + if (b == -1) + throw new Exception("Reached end of stream while reading HTTP response status"); + versionString = new String(linebuf, 0, versionEnd, ISO_8859_1); + statusCode = Integer.parseInt(new String(linebuf, codeStart, codeEnd - codeStart, ISO_8859_1)); + statusString = new String(linebuf, stringStart, count - stringStart, ISO_8859_1); + } catch (Exception e) { throw new SOAPException(Constants.FAULT_CODE_CLIENT, - "Error parsing HTTP status line \"" + line + "\": " + e, e); + "Error parsing HTTP status line \"" + new String(linebuf, 0, count, ISO_8859_1) + "\": " + e, 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(); + /* Read the HTTP headers. */ Hashtable respHeaders = new Hashtable(); int respContentLength = -1; String respContentType = null; - int nameStart = 0; - int nameEnd = 0; - int valStart = 0; - boolean parsingName = true; - int offset; - - for (offset = 0; offset < bytes.length; offset++) { - if (bytes[offset] == '\n') { - if (nameStart >= nameEnd) + try { + // Read all headers + for (;;) { + // Read and parse one header + int nameEnd = -1; + int valStart = -1; + for (count = 0, b = bInStream.read(); b != '\n' && b != -1; b = bInStream.read()) { + if (b != '\r') { + if (nameEnd == -1 && b == ':') { + nameEnd = count; + } else if (nameEnd != -1 && valStart == -1 && b != ' ' & b != '\t') { + valStart = count; + } + if (count >= linebuf.length) { + byte[] newbuf = new byte[linebuf.length * 2]; + System.arraycopy(linebuf, 0, newbuf, 0, linebuf.length); + linebuf = newbuf; + } + linebuf[count++] = (byte) b; + } + } + if (b == -1) + throw new Exception("Reached end of stream while reading HTTP response header"); + if (count == 0) // Read the header/entity separator break; - String name = new String(bytes, nameStart, nameEnd-nameStart+1); - - // Remove trailing ; to prevent ContextType from throwing exception - int valueLen = offset - valStart -1; + if (nameEnd == -1 || valStart == -1) + throw new Exception("Incorrectly formed HTTP response header"); - if (valueLen > 0 && bytes[offset-1] == ';') - valueLen--; + String name = new String(linebuf, 0, nameEnd, ISO_8859_1); + // Remove trailing ; to prevent ContentType from throwing exception + if (linebuf[count - 1] == ';') + --count; + String value = new String(linebuf, valStart, count - valStart, ISO_8859_1); - String value = new String(bytes, valStart, valueLen); if (name.equalsIgnoreCase(Constants.HEADER_CONTENT_LENGTH)) respContentLength = Integer.parseInt(value); else if (name.equalsIgnoreCase(Constants.HEADER_CONTENT_TYPE)) @@ -556,27 +594,11 @@ } } } - parsingName = true; - nameStart = offset+1; } - else if (bytes[offset] != '\r') { - if (parsingName) { - if (bytes[offset] == ':') { - parsingName = false; - nameEnd = offset - 1; - if ((offset != bytes.length-1) && - bytes[offset+1] == ' ') - offset++; - valStart = offset+1; - } - } - } - } // End of for - - InputStream is = ds.getInputStream(); - is.skip(offset + 1); - if (respContentLength < 0) - respContentLength = ds.getSize() - offset - 1; + } catch (Exception e) { + throw new SOAPException(Constants.FAULT_CODE_CLIENT, + "Error parsing HTTP header line \"" + new String(linebuf, 0, count, ISO_8859_1) + "\": " + e, e); + } /* Handle redirect here */ if (statusCode >= HttpURLConnection.HTTP_MULT_CHOICE && @@ -595,11 +617,6 @@ } } - /* If required, capture a copy of the response. */ - if (responseCopy != null) { - responseCopy.append(line).append("\r\n").append(new String(bytes)); /* May get junk due to actual encoding */ - } - // TODO: process differently depending on statusCode and respContentLength // (TransportMessage does not even get statusCode) // e.g. statusCode 401 is Unauthorized @@ -612,13 +629,18 @@ // Create response SOAPContext. ctx = new SOAPContext(); // Read content. - response = new TransportMessage(is, respContentLength, + response = new TransportMessage(bInStream, respContentLength, respContentType, ctx, respHeaders); // Extract envelope and SOAPContext response.read(); } catch (MessagingException me) { throw new SOAPException(Constants.FAULT_CODE_CLIENT, "Error parsing response: " + me, me); + } + + /* If required, capture a copy of the response. */ + if (responseCopy != null) { + responseCopy.append(new String(response.getBytes())); /* May get junk due to actual encoding */ } /* All done here! */ 1.17 +51 -37 xml-soap/java/src/org/apache/soap/transport/TransportMessage.java Index: TransportMessage.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/transport/TransportMessage.java,v retrieving revision 1.16 retrieving revision 1.17 diff -u -r1.16 -r1.17 --- TransportMessage.java 6 Sep 2002 17:02:58 -0000 1.16 +++ TransportMessage.java 12 Nov 2002 14:15:38 -0000 1.17 @@ -128,25 +128,36 @@ this.ctx = ctx; this.contentType = contentType; - if (contentLength < 0) - throw new SOAPException (Constants.FAULT_CODE_PROTOCOL, - "Content length must be specified."); - - bytes = new byte[contentLength]; - int offset = 0; - int bytesRead = 0; - - // We're done reading when we get all the content OR when the stream - // returns a -1. - while ((offset < contentLength) && (bytesRead >= 0)) { - bytesRead = is.read(bytes, offset, contentLength - offset); - offset += bytesRead; - } - if (offset < contentLength) - throw new SOAPException (Constants.FAULT_CODE_PROTOCOL, - "Premature end of stream. Data is truncated. Read " - + offset + " bytes successfully, expected " - + contentLength); + bytes = new byte[contentLength >= 0 ? contentLength : 4096]; + if (contentLength != 0) { + int offset = 0; + int bytesRead = 0; + + // We're done reading when we get all the content OR when the stream + // returns a -1. + while ((contentLength < 0 || offset < contentLength) && (bytesRead >= 0)) { + bytesRead = is.read(bytes, offset, bytes.length - offset); + offset += bytesRead; + if (contentLength < 0 && offset >= bytes.length) { + byte[] newbuf = new byte[bytes.length * 2]; + System.arraycopy(bytes, 0, newbuf, 0, bytes.length); + bytes = newbuf; + } + } + + if (contentLength < 0) { + if (offset < bytes.length) { + byte[] newbuf = new byte[offset]; + System.arraycopy(bytes, 0, newbuf, 0, offset); + bytes = newbuf; + } + } else if (offset < contentLength) { + throw new SOAPException (Constants.FAULT_CODE_PROTOCOL, + "Premature end of stream. Data is truncated. Read " + + offset + " bytes successfully, expected " + + contentLength); + } + } } /** @@ -284,7 +295,7 @@ } // If the root part is text, extract it as a String. - // Note that we could use JAF's help to do this (see getEnvelope()) + // Note that we could use JAF's help to do this (see save()) // but implementing it ourselves is safer and faster. if (rootContentType.match("text/*")) { String charset = rootContentType.getParameter("charset"); @@ -323,9 +334,11 @@ */ public void save() throws MessagingException, IOException { - /* If an envelope was provided as a string, set it as the root part. - * Otherwise, assume that the SOAPContext already has a root part. + /* + * If an envelope was provided as a string, set it as the root part. * If there was already a root part, preserve its content-type. + * Otherwise, assume that the SOAPContext already has a root part, + * and try to use it as the envelope. */ String rootContentType = null; if (ctx.isRootPartSet()) { @@ -339,8 +352,18 @@ } if (rootContentType == null) rootContentType = Constants.HEADERVAL_CONTENT_TYPE_UTF8; - if (getEnvelope() != null) + if (getEnvelope() != null) { ctx.setRootPart(envelope, rootContentType); + } else { + MimeBodyPart rootPart = ctx.getRootPart(); + if (rootPart != null) { + if (rootPart.isMimeType("text/*")) { + ByteArrayDataSource ds = new ByteArrayDataSource( + rootPart.getInputStream(), rootPart.getContentType()); + envelope = ds.getText(); + } + } + } // Print the whole response to a byte array. ByteArrayOutputStream payload = @@ -428,20 +451,9 @@ } /** - * Get SOAP Envelope/root part as a String. - * This method will extract the root part from the SOAPContext as a String - * if there is no SOAP Envelope. + * Get SOAP Envelope as a String. */ - public String getEnvelope() throws MessagingException, IOException { - if (envelope == null) { - MimeBodyPart rootPart = ctx.getRootPart(); - if (rootPart != null) - if (rootPart.isMimeType("text/*")) { - ByteArrayDataSource ds = new ByteArrayDataSource( - rootPart.getInputStream(), rootPart.getContentType()); - envelope = ds.getText(); - } - } + public String getEnvelope() { return envelope; } @@ -449,7 +461,7 @@ * Get SOAP Envelope/root part as a Reader. Returns null if the root part * is not text. */ - public Reader getEnvelopeReader() throws MessagingException, IOException { + public Reader getEnvelopeReader() { if (getEnvelope() == null) return null; else @@ -530,6 +542,8 @@ /** * Set the byte array of the response. + * + * @deprecated After 2.3.1 */ public void readFully(InputStream is) throws IOException { offset = 0; 1.29 +19 -16 xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java Index: SOAPHTTPConnection.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java,v retrieving revision 1.28 retrieving revision 1.29 diff -u -r1.28 -r1.29 --- SOAPHTTPConnection.java 16 Oct 2002 04:16:15 -0000 1.28 +++ SOAPHTTPConnection.java 12 Nov 2002 14:15:38 -0000 1.29 @@ -84,9 +84,7 @@ * @author Arek Wnukowski ([EMAIL PROTECTED]) */ public class SOAPHTTPConnection implements SOAPTransport { - private BufferedReader responseReader; - private Hashtable responseHeaders; - private SOAPContext responseSOAPContext; + private TransportMessage response; private String httpProxyHost; private int httpProxyPort = 80; @@ -369,7 +367,6 @@ "Basic " + HTTPUtils.encodeAuth(proxyUserName, proxyPassword)); } - TransportMessage response; try { TransportMessage msg = new TransportMessage(payload, ctx, headers); @@ -385,18 +382,12 @@ throw new IOException ("Failed to encode mime multipart: " + uee); } - Reader envReader = response.getEnvelopeReader(); - if (envReader != null) - responseReader = new BufferedReader(envReader); - else - responseReader = null; - responseSOAPContext = response.getSOAPContext(); - responseHeaders = response.getHeaders(); if (maintainSession) { // look for Set-Cookie2 and Set-Cookie headers and save them. // Only update my state iff the header is there .. otherwise // leave the current // Note: Header is case-insensitive + Hashtable responseHeaders = response.getHeaders(); String hdr; hdr = HTTPUtils.getHeaderValue (responseHeaders, "Set-Cookie2"); @@ -421,8 +412,6 @@ } } catch (IllegalArgumentException e) { throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); - } catch (MessagingException e) { - throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); } catch (IOException e) { throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); } @@ -436,7 +425,21 @@ * possible. */ public BufferedReader receive () { - return responseReader; + if (response != null) { + Reader envReader = response.getEnvelopeReader(); + if (envReader != null) + return new BufferedReader(envReader); + } + return null; + } + + /** + * Returns the SOAP envelope. + * + * @return The SOAP envelope. + */ + public String getEnvelope() { + return response != null ? response.getEnvelope() : null; } /** @@ -445,7 +448,7 @@ * @return a hashtable containing all the headers */ public Hashtable getHeaders () { - return responseHeaders; + return response != null ? response.getHeaders() : null; } /** @@ -454,6 +457,6 @@ * @return response SOAPContext */ public SOAPContext getResponseSOAPContext () { - return responseSOAPContext; + return response != null ? response.getSOAPContext() : null; } }
-- To unsubscribe, e-mail: <mailto:soap-dev-unsubscribe@;xml.apache.org> For additional commands, e-mail: <mailto:soap-dev-help@;xml.apache.org>