Bug fixes: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11095
Changelog: - Abstract EntityEnclosingMethod class has been introduced to encapsulate common behaviour of all entity enclosing methods - Limited "Expect: 100-continue" support in all entity enclosing methods (HttpClient hangs indefinitely if status code 100 is not sent when expected) - Support for chunk encoded requests in all entity enclosing methods - More robust (or so I'd like to hope) request content buffering logic - PostMethod inherited from EntityEnclosingMethod class - PutMethod inherited from EntityEnclosingMethod class To be done next: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11653 http://nagoya.apache.org/bugzilla/show_bug.cgi?id=14731 Feedback, critique welcome as always. Feel free to start throwing bad tomatoes at me Cheers Oleg
Index: java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java =================================================================== RCS file: java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java diff -N java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java 29 Jan +2003 16:12:02 -0000 @@ -0,0 +1,512 @@ +/* + * $Header: +/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java,v + 1.21 2003/01/23 22:48:06 jsdever Exp $ + * $Revision: 1.21 $ + * $Date: 2003/01/23 22:48:06 $ + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 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 acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", 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 names without prior written + * permission of the Apache Group. + * + * 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. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient.methods; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.Reader; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.httpclient.HttpConstants; +import org.apache.commons.httpclient.HttpConnection; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.ChunkedOutputStream; +import org.apache.commons.httpclient.ContentLengthInputStream; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This abstract class serves as a foundation for all HTTP methods + * that can enclose an entity within requests + * + * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Doug Sale</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Dever</a> + * @author Ortwin Glück + * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> + * @since 2.0 + */ + +public abstract class EntityEnclosingMethod extends GetMethod +{ + // ----------------------------------------- Static variables/initializers + + /** + * The content length will be calculated automatically. This implies + * buffering of the content. + */ + public static final int CONTENT_LENGTH_AUTO = -2; + + /** + * The request will use chunked transfer encoding. Content length is not + * calculated and the content is not buffered.<br> + */ + public static final int CONTENT_LENGTH_CHUNKED = -1; + + /** Log object for this class. */ + private static final Log log = LogFactory.getLog(EntityEnclosingMethod.class); + + /** The buffered request body, if any. */ + private byte[] buffer = null; + + /** The unbuffered request body, if any. */ + private InputStream requestBodyStream = null; + + /** Counts how often the request was sent to the server. */ + private int repeatCount = 0; + + /** The content length of the <code>requestBodyStream</code> or one of + * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>. + */ + private int requestContentLength = CONTENT_LENGTH_AUTO; + + private boolean useExpectHeader = true; + + // ----------------------------------------------------------- Constructors + + /** + * No-arg constructor. + * + * @since 2.0 + */ + public EntityEnclosingMethod() { + super(); + setFollowRedirects(false); + } + + /** + * Constructor specifying a URI. + * + * @param uri either an absolute or relative URI + * + * @since 2.0 + */ + public EntityEnclosingMethod(String uri) { + super(uri); + setFollowRedirects(false); + } + + /** + * Constructor specifying a URI and a tempDir. + * + * @param uri either an absolute or relative URI + * @param tempDir directory to store temp files in + * + * @since 2.0 + */ + public EntityEnclosingMethod(String uti, String tempDir) { + super(uti, tempDir); + setFollowRedirects(false); + } + + /** + * Constructor specifying a URI, tempDir and tempFile. + * + * @param uri either an absolute or relative URI + * @param tempDir directory to store temp files in + * @param tempFile file to store temporary data in + * + * @since 2.0 + */ + public EntityEnclosingMethod(String uri, String tempDir, String tempFile) { + super(uri, tempDir, tempFile); + setFollowRedirects(false); + } + + + /** + * Returns the useExpectHeader. + * @return boolean + */ + public boolean getUseExpectHeader() + { + return this.useExpectHeader; + } + + /** + * Sets the useExpectHeader. + * @param useExpectHeader The useExpectHeader to set + */ + public void setUseExpectHeader(boolean value) + { + this.useExpectHeader = value; + } + + /** + * Returns true if request body has been buffered. Otherwise returns false. + * + * @since 2.0 + */ + + protected boolean hasBufferedRequestBody() { + return this.buffer != null; + } + + /** + * Returns true if request body has been set. Otherwise returns false. + * + * @since 2.0 + */ + + protected boolean hasRequestBody() { + return (this.requestBodyStream != null) || (this.buffer != null); + } + + /** + * Sets length information about the request body. + * + * <p> + * Note: If you specify a content length the request is unbuffered. This + * prevents redirection and automatic retry if a request fails the first + * time. This means that the HttpClient can not perform authorization + * automatically but will throw an Exception. You will have to set the + * necessary 'Authorization' or 'Proxy-Authorization' headers manually. + * </p> + * + * @param length size in bytes or any of CONTENT_LENGTH_AUTO, + * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED + * is specified the content will not be buffered internally and the + * Content-Length header of the request will be used. In this case + * the user is responsible to supply the correct content length. + * If CONTENT_LENGTH_AUTO is specified the request will be buffered + * before it is sent over the network. + * + * @since 2.0 + */ + + public void setRequestContentLength(int length) { + log.trace("enter EntityEnclosingMethod.setRequestContentLength(int)"); + this.requestContentLength = length; + } + + /** + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to return the length of the request body. + * + * @return number of bytes in the request body + * + * @since 2.0 + */ + + protected int getRequestContentLength() { + log.trace("enter EntityEnclosingMethod.getRequestContentLength()"); + + if (this.requestContentLength != CONTENT_LENGTH_AUTO) { + return this.requestContentLength; + } + bufferContent(); + if (this.buffer != null) { + return this.buffer.length; + } + else { + return 0; + } + } + + /** + * Sets the request body to be the specified inputstream. + * + * @param body Request body content as {@link java.io.InputStream} + * + * @since 2.0 + */ + + public void setRequestBody(InputStream body) { + log.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)"); + this.requestBodyStream = body; + this.buffer = null; + } + + /** + * Gets the request body as a stream. + * + * @return The request body {@link java.io.InputStream} if it has been set. + * + * @throws IllegalStateException if request body is not buferred + * + * @since 2.0 + */ + + public InputStream getRequestBody() { + log.trace("enter EntityEnclosingMethod.getRequestBody()"); + if (this.buffer != null) { + return new ByteArrayInputStream(this.buffer); + } + else { + return this.requestBodyStream; + } + } + + /** + * Sets the request body to be the specified string. + * + * @param body Request body content as a string + * + * @since 2.0 + */ + + public void setRequestBody(String body) { + log.trace("enter EntityEnclosingMethod.setRequestBody(String)"); + + if (body == null) { + this.requestBodyStream = null; + this.buffer = null; + return; + } + this.buffer = HttpConstants.getContentBytes(body, getRequestCharSet()); + } + + /** + * Gets the request body as a string. + * + * @return the request body as a string + * + * @throws IOException when i/o errors occur reading the request + * @throws IllegalStateException if request body is not buferred + * + * @since 2.0 + */ + + public String getRequestBodyAsString() throws IOException { + log.trace("enter EntityEnclosingMethod.getRequestBodyAsString()"); + Reader instream = null; + try { + instream = new InputStreamReader(getRequestBody(), getRequestCharSet()); + } + catch(UnsupportedEncodingException e) { + if (log.isWarnEnabled()) { + log.warn("Unsupported encoding: " + e.getMessage()); + } + instream = new InputStreamReader(getRequestBody()); + } + StringBuffer buffer = new StringBuffer(); + char[] tmp = new char[4096]; + int l = 0; + while((l = instream.read(tmp)) >= 0) { + buffer.append(tmp, 0, l); + } + return buffer.toString(); + } + + + /** + * Override the method of {@link HttpMethodBase} + * to set the <tt>Expect</tt> header if it has + * not already been set, in addition to the "standard" + * set of headers. + * + * @throws HttpException when a protocol error occurs or state is invalid + * @throws IOException when i/o errors occur reading the response + * + * @since 2.0 + */ + + protected void addRequestHeaders(HttpState state, HttpConnection conn) + throws IOException, HttpException { + log.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, +HttpConnection)"); + + if (!isHttp11() && getUseExpectHeader()) { + throw new HttpException( + "100-continue not allowed for HTTP/1.0"); + } + + super.addRequestHeaders(state, conn); + // Send expectation header, provided there's something to be sent + if(isHttp11() && hasRequestBody()) { + if (getRequestHeader("Expect") == null) { + setRequestHeader("Expect", "100-continue"); + } + } + } + + /** + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to write request parameters as the request body. The input stream will + * be truncated after the specified content length. + * + * @param state the client state + * @param conn the connection to write to + * + * @return <tt>true</tt> + * @throws IOException when i/o errors occur reading the response + * @throws HttpException when a protocol error occurs or state is invalid + * + * @since 2.0 + */ + + protected boolean writeRequestBody(HttpState state, HttpConnection conn) + throws IOException, HttpException { + log.trace( + "enter EntityEnclosingMethod.writeRequestBody(HttpState, +HttpConnection)"); + + if (getStatusLine() == null) { + return false; + } + if (getRequestHeader("Expect") != null) { + if (getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) { + return false; + } + } + + int contentLength = getRequestContentLength(); + + if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) { + throw new HttpException( + "Chunked transfer encoding not allowed for HTTP/1.0"); + } + InputStream instream = getRequestBody(); + if (instream == null) { + return true; + } + + if ((this.repeatCount > 0) && (this.buffer == null)) { + throw new HttpException( + "Unbuffered entity enclosing request can not be repeated."); + } + + this.repeatCount++; + + OutputStream outstream = conn.getRequestOutputStream(); + if (contentLength == CONTENT_LENGTH_CHUNKED) { + outstream = new ChunkedOutputStream(outstream); + } + if (contentLength >= 0) { + // don't need a watcher here - we're reading from something local, + // not server-side. + instream = new ContentLengthInputStream(instream, contentLength); + } + + byte[] tmp = new byte[4096]; + int total = 0; + int i = 0; + while ((i = instream.read(tmp)) >= 0) { + outstream.write(tmp, 0, i); + total += i; + } + // This is hardly the most elegant solution to closing chunked stream + if (outstream instanceof ChunkedOutputStream) { + ((ChunkedOutputStream)outstream).writeClosingChunk(); + } + if ((contentLength > 0) && (total < contentLength)) { + throw new IOException("Unexpected end of input stream after " + + total + " bytes (expected " + contentLength + " bytes)"); + } + return true; + } + + /** + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to clear my request body. + * + * @since 2.0 + */ + public void recycle() { + log.trace("enter EntityEnclosingMethod.recycle()"); + super.recycle(); + this.requestContentLength = CONTENT_LENGTH_AUTO; + this.requestBodyStream = null; + this.buffer = null; + this.repeatCount = 0; + } + + /** + * Buffers the request body and calculates the content length. If the + * method was called earlier it returns immediately. + * + * @since 2.0 + */ + + protected void bufferContent() { + log.trace("enter EntityEnclosingMethod.bufferContent()"); + + if (this.buffer != null) { + return; + } + if (this.requestBodyStream == null) { + return; + } + try { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + byte[] data = new byte[4096]; + int l = 0; + while ((l = this.requestBodyStream.read(data)) >= 0) { + tmp.write(data, 0, l); + } + this.buffer = tmp.toByteArray(); + this.requestBodyStream = null; + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error(e.toString(), e ); + } + this.buffer = null; + this.requestBodyStream = null; + } + } +} Index: java/org/apache/commons/httpclient/methods/PostMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v retrieving revision 1.34 diff -u -r1.34 PostMethod.java --- java/org/apache/commons/httpclient/methods/PostMethod.java 28 Jan 2003 22:25:26 -0000 1.34 +++ java/org/apache/commons/httpclient/methods/PostMethod.java 29 Jan 2003 16:12:02 +-0000 @@ -1,8 +1,7 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v 1.34 2003/01/28 22:25:26 jsdever Exp $ - * $Revision: 1.34 $ - * $Date: 2003/01/28 22:25:26 $ - * + * $Header: +/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v + 1.33 2003/01/23 22:48:08 jsdever Exp $ + * $Revision: 1.33 $ + * $Date: 2003/01/23 22:48:08 $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -60,27 +59,21 @@ * [Additional notices, if required by prior licensing conditions] * */ - package org.apache.commons.httpclient.methods; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; +import java.io.ByteArrayInputStream; +import java.util.Vector; import java.util.Iterator; import java.util.List; -import java.util.Vector; - import org.apache.commons.httpclient.HttpConstants; -import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.URIException; -import org.apache.commons.httpclient.ChunkedOutputStream; -import org.apache.commons.httpclient.ContentLengthInputStream; import org.apache.commons.httpclient.util.URIUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -116,54 +109,24 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> * @author <a href="mailto:[EMAIL PROTECTED]">Doug Sale</a> * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Dever</a> - * @author Ortwin Gl� - * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> + * @author Ortwin Glück + * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> * @since 1.0 */ -public class PostMethod extends GetMethod { - //~ Static variables/initializers ������������������������������������������ - - /** - * The content length will be calculated automatically. This implies - * buffering of the content. - */ - public static final int CONTENT_LENGTH_AUTO = -2; - - /** - * The request will use chunked transfer encoding. Content length is not - * calculated and the content is not buffered.<br> - */ - public static final int CONTENT_LENGTH_CHUNKED = -1; - +public class PostMethod extends EntityEnclosingMethod { // -------------------------------------------------------------- Constants /** Log object for this class. */ - private static final Log LOG = LogFactory.getLog(PostMethod.class); + private static final Log log = LogFactory.getLog(PostMethod.class); /** The Content-Type header for www-form-urlcoded. */ - private static final Header CONTENT_TYPE = new Header("Content-Type", + static final Header CONTENT_TYPE = new Header("Content-Type", "application/x-www-form-urlencoded"); - /** The buffered request body. */ - protected ByteArrayOutputStream buffer = null; - - /** The unbuffered request body. */ - protected InputStream requestBody = null; - /** The buffered request body consisting of <code>NameValuePair</code>s */ protected Vector parameters = new Vector(); - /** Counts how often the request was sent to the server. */ - protected int repeatCount = 0; - - /** - * The content length of the <code>requestBody</code> or one of - * <code>{@link #CONTENT_LENGTH_AUTO}</code> and - * <code>{@link #CONTENT_LENGTH_CHUNKED}</code>. - */ - protected int requestContentLength = CONTENT_LENGTH_AUTO; - - //~ Constructors ����������������������������������������������������������� + // ----------------------------------------------------------- Constructors /** * No-arg constructor. @@ -195,8 +158,8 @@ * * @since 1.0 */ - public PostMethod(String uri, String tempDir) { - super(uri, tempDir); + public PostMethod(String uti, String tempDir) { + super(uti, tempDir); setFollowRedirects(false); } @@ -216,8 +179,6 @@ // ----------------------------------------------------------- Constructors - //~ Methods ���������������������������������������������������������������� - /** * A POST request can only be redirected if input is buffered. Overrides * method of {@link org.apache.commons.httpclient.HttpMethodBase}. @@ -228,11 +189,7 @@ * @since 2.0 */ public boolean getFollowRedirects() { - if (!super.getFollowRedirects()) { - return false; - } - - return (buffer != null); + return (hasBufferedRequestBody() && super.getFollowRedirects()); } // ----------------------------------------------------- Instance Methods @@ -252,24 +209,23 @@ * Set the value of parameter with parameterName to parameterValue. Does * not preserve the initial insertion order. * - * @param parameterName DOCUMENT ME! - * @param parameterValue DOCUMENT ME! + * @param parameterName name of the parameter + * @param parameterValue value of the parameter * * @throws IllegalStateException if my request body has already been * generated. * * @since 2.0 + * * @deprecated use {@link #removeParameter(String,String)} followed by * {@link #addParameter(String,String)}. */ - public void setParameter(String parameterName, String parameterValue) - throws IllegalStateException { - LOG.trace("enter PostMethod.setParameter(String, String)"); + public void setParameter(String parameterName, String parameterValue) { + log.trace("enter PostMethod.setParameter(String, String)"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } - removeParameter(parameterName, parameterValue); addParameter(parameterName, parameterValue); } @@ -278,7 +234,7 @@ * Gets the parameter of the specified name. If there exists more than one * parameter with the name paramName, then only the first one is returned. * - * @param paramName DOCUMENT ME! + * @param paramName name of the parameter * * @return If a parameter exists with the name argument, the coresponding * NameValuePair is returned. Otherwise null. @@ -286,7 +242,7 @@ * @since 2.0 */ public NameValuePair getParameter(String paramName) { - LOG.trace("enter PostMethod.getParameter(String)"); + log.trace("enter PostMethod.getParameter(String)"); if (paramName == null) { return null; @@ -301,7 +257,6 @@ return parameter; } } - return null; } @@ -317,7 +272,7 @@ * @see #getParameter(java.lang.String) */ public NameValuePair[] getParameters() { - LOG.trace("enter PostMethod.getParameters()"); + log.trace("enter PostMethod.getParameters()"); int numPairs = parameters.size(); Object[] objectArr = parameters.toArray(); @@ -331,142 +286,6 @@ } /** - * Sets the request body to be the specified string. - * - * <p> - * Once this method has been invoked, the request parameters cannot be - * altered until I am {@link #recycle recycled}. - * </p> - * - * @param body Request content as a string - * - * @throws IllegalStateException if request params have been added - * - * @since 2.0 - */ - public void setRequestBody(String body) throws IllegalStateException { - LOG.trace("enter PostMethod.setRequestBody(String)"); - - if (!parameters.isEmpty()) { - throw new IllegalStateException( - "Request parameters have already been added."); - } - - if (body == null) { - this.requestBody = null; - return; - } - - this.requestBody = new ByteArrayInputStream( - HttpConstants.getContentBytes(body, getRequestCharSet())); - } - - /** - * Sets the request body to be the specified inputstream. - * - * <p> - * Once this method has been invoked, the request parameters cannot be - * altered until I am {@link #recycle recycled}. - * </p> - * - * @param body DOCUMENT ME! - * - * @throws IllegalStateException if request params have been added - * - * @since 2.0 - */ - public void setRequestBody(InputStream body) throws IllegalStateException { - LOG.trace("enter PostMethod.getRequestBody(InputStream)"); - - if (!parameters.isEmpty()) { - throw new IllegalStateException( - "Request parameters have already been added."); - } - - this.requestBody = body; - } - - /** - * Gets the requestBody as it would be if it was executed. - * - * @return The request body if it has been set. The generated request - * body from the paramters if they exist. Null otherwise. - * - * @since 2.0 - */ - public InputStream getRequestBody() { - LOG.trace("enter PostMethod.getRequestBody()"); - - if (requestBody != null) { - return requestBody; - } else if (!parameters.isEmpty()) { - return generateRequestBody(parameters); - } else { - return null; - } - } - - /** - * Return the request body as a string. - * - * @return the request body as a string - * @throws IOException If an IO problem occurs. - * - * @since 2.0 - */ - public String getRequestBodyAsString() throws IOException { - LOG.trace("enter PostMethod.getRequestBodyAsString()"); - - StringBuffer buffer = new StringBuffer(); - InputStream requestBody = getRequestBody(); - int data = requestBody.read(); - - while (data != -1) { - buffer.append((char) data); - data = requestBody.read(); - } - - return buffer.toString(); - } - - /** - * Sets length information about the request body. - * - * <p> - * Note: If you specify a content length the request is unbuffered. This - * prevents redirection and automatic retry if a request fails the first - * time. This means that the HttpClient can not perform authorization - * automatically but will throw an Exception. You will have to set the - * necessary 'Authorization' or 'Proxy-Authorization' headers manually. - * </p> - * - * @param length size in bytes or any of CONTENT_LENGTH_AUTO, - * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED - * is specified the content will not be buffered internally and the - * Content-Length header of the request will be used. In this case - * the user is responsible to supply the correct content length. - * If CONTENT_LENGTH_AUTO is specified the request will be buffered - * before it is sent over the network. - * @throws RuntimeException if chunked transfer encoding is requested for - * a HTTP 1.0 request - * - * @since 2.0 - */ - public void setRequestContentLength(int length) - throws RuntimeException { - //TODO: We should be throwing a more specific exception than this. - - LOG.trace("enter PostMethod.setRequestContentLength(int)"); - - if ((length == CONTENT_LENGTH_CHUNKED) && !isHttp11()) { - throw new RuntimeException( - "Chunked transfer encoding not allowed for HTTP/1.0"); - } - - requestContentLength = length; - } - - /** * Add a new parameter to be used in the POST request body. * * @param paramName The parameter name to add. @@ -478,12 +297,10 @@ * * @since 1.0 */ - public void addParameter(String paramName, String paramValue) - throws IllegalStateException, IllegalArgumentException { - - LOG.trace("enter PostMethod.addParameter(String, String)"); + public void addParameter(String paramName, String paramValue) { + log.trace("enter PostMethod.addParameter(String, String)"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } @@ -508,18 +325,14 @@ * @since 2.0 * @see #addParameter(String,String) */ - public void addParameter(NameValuePair param) - throws IllegalStateException, IllegalArgumentException { - - LOG.trace("enter PostMethod.addParameter(NameValuePair)"); + public void addParameter(NameValuePair param) { + log.trace("enter PostMethod.addParameter(NameValuePair)"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } - - if (null == param) { - throw new IllegalArgumentException( - "Argument to addParameter(NameValuePair) cannot be null"); + if (param == null) { + throw new IllegalArgumentException("NameValuePair may not be null"); } else { addParameter(param.getName(), param.getValue()); } @@ -537,17 +350,14 @@ * @since 2.0 * @see #addParameter(org.apache.commons.httpclient.NameValuePair) */ - public void addParameters(NameValuePair[] parameters) - throws IllegalStateException { - - LOG.trace("enter PostMethod.addParameters(NameValuePair[])"); + public void addParameters(NameValuePair[] parameters) { + log.trace("enter PostMethod.addParameters(NameValuePair[])"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } - - if (null == parameters) { - LOG.warn("Attempt to addParameters(null) ignored"); + if (parameters == null) { + log.warn("Attempt to addParameters(null) ignored"); } else { for (int i = 0; i < parameters.length; i++) { addParameter(parameters[i]); @@ -556,22 +366,6 @@ } /** - * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} - * to clear my request body. - * - * @since 1.0 - */ - public void recycle() { - LOG.trace("enter PostMethod.recycle()"); - super.recycle(); - requestBody = null; - requestContentLength = CONTENT_LENGTH_AUTO; - buffer = null; - repeatCount = 0; - parameters.clear(); - } - - /** * Removes all parameters with the given paramName. If there is more than * one parameter with the given paramName, all of them are removed. If * there is just one, it is removed. If there are none, then the request @@ -587,20 +381,16 @@ * * @since 2.0 */ - public boolean removeParameter(String paramName) - throws IllegalArgumentException, IllegalStateException { - - LOG.trace("enter PostMethod.removeParameter(String)"); + public boolean removeParameter(String paramName) { + log.trace("enter PostMethod.removeParameter(String)"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } - if (paramName == null) { throw new IllegalArgumentException( "Argument passed to removeParameter(String) cannot be null"); } - boolean removed = true; Iterator iter = parameters.iterator(); @@ -612,7 +402,6 @@ removed = true; } } - return removed; } @@ -632,19 +421,17 @@ * * @since 2.0 */ - public boolean removeParameter(String paramName, String paramValue) - throws IllegalArgumentException, IllegalStateException { - - LOG.trace("enter PostMethod.removeParameter(String, String)"); + public boolean removeParameter(String paramName, String paramValue) { + log.trace("enter PostMethod.removeParameter(String, String)"); - if (null != requestBody) { + if (hasRequestBody()) { throw new IllegalStateException("Request body already generated."); } - - if ((paramName == null) || (paramValue == null)) { - throw new IllegalArgumentException( - "Argument passed to removeParameter(String,String) cannot be " - + "null"); + if (paramName == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (paramValue == null) { + throw new IllegalArgumentException("Parameter value may not be null"); } Iterator iter = parameters.iterator(); @@ -655,221 +442,220 @@ if (paramName.equals(pair.getName()) && paramValue.equals(pair.getValue())) { iter.remove(); - return true; } } - return false; } /** - * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} - * to return the length of the request body. + * Encode the list of parameters into a query string. * - * @return number of bytes in the request body + * @param params the list of query name and value + * + * @return the query string * * @since 2.0 */ - protected int getRequestContentLength() { - LOG.trace("enter PostMethod.getRequestContentLength()"); + protected static String generateRequestBody(List params) { + log.trace("enter PostMethod.generateRequestBodyAsString(List)"); - if (null == requestBody) { - requestBody = generateRequestBody(parameters); - bufferContent(); - } - - if (requestContentLength != CONTENT_LENGTH_AUTO) { - return requestContentLength; - } + Iterator it = params.iterator(); + StringBuffer buff = new StringBuffer(); - bufferContent(); + while (it.hasNext()) { + NameValuePair parameter = (NameValuePair) it.next(); - return requestContentLength; + String queryName = null; + try { + queryName = URIUtil.encodeWithinQuery(parameter.getName()); + } catch (URIException urie) { + log.error("encoding error within query name", urie); + queryName = parameter.getName(); + } + buff.append(queryName).append("="); + String queryValue = null; + try { + queryValue = URIUtil.encodeWithinQuery(parameter.getValue()); + } catch (URIException urie) { + log.error("encoding error within query value", urie); + queryValue = parameter.getValue(); + } + buff.append(queryValue); + if (it.hasNext()) { + buff.append("&"); + } + } + return buff.toString(); } /** - * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} - * to also add <tt>Content-Type</tt> header when appropriate. + * Sets the request body to be the specified string. * - * @param state DOCUMENT ME! - * @param conn DOCUMENT ME! - * @throws IOException DOCUMENT ME! - * @throws HttpException DOCUMENT ME! + * <p> + * Once this method has been invoked, the request parameters cannot be + * altered until I am {@link #recycle recycled}. + * </p> + * + * @param body Request body content as a string + * + * @throws IllegalStateException if request params have been added * * @since 2.0 */ - protected void addRequestHeaders(HttpState state, HttpConnection conn) - throws IOException, HttpException { - super.addRequestHeaders(state, conn); + public void setRequestBody(String body) { + log.trace("enter PostMethod.setRequestBody(String)"); if (!parameters.isEmpty()) { - //there are some parameters, so set the contentType header - setRequestHeader(CONTENT_TYPE); + throw new IllegalStateException( + "Request parameters have already been added."); } + super.setRequestBody(body); } /** - * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} - * to write request parameters as the request body. The input stream will - * be truncated after the specified content length. + * Sets the request body to be the specified inputstream. * - * @param state DOCUMENT ME! - * @param conn DOCUMENT ME! + * <p> + * Once this method has been invoked, the request parameters cannot be + * altered until I am {@link #recycle recycled}. + * </p> * - * @return always returns true + * @param body Request body content as {@link java.io.InputStream} * - * @throws IOException if the stream ends before the specified content - * length. <p> - * @throws HttpException DOCUMENT ME! + * @throws IllegalStateException if request params have been added * * @since 2.0 */ - protected boolean writeRequestBody(HttpState state, HttpConnection conn) - throws IOException, HttpException { - LOG.trace( - "enter PostMethod.writeRequestBody(HttpState, HttpConnection)"); + public void setRequestBody(InputStream body) { + log.trace("enter PostMethod.getRequestBody(InputStream)"); - if (null == requestBody) { - requestBody = generateRequestBody(parameters); + if (!parameters.isEmpty()) { + throw new IllegalStateException( + "Request parameters have already been added."); } + super.setRequestBody(body); + } - if ((repeatCount > 0) && (buffer == null)) { - throw new HttpException( - "Sorry, unbuffered POST request can not be repeated."); + /** + * Gets the requestBody as it would be if it was executed. + * + * @return The request body if it has been set. The generated request + * body from the paramters if they exist. Null otherwise. + * + * @since 2.0 + */ + public InputStream getRequestBody() { + log.trace("enter PostMethod.getRequestBody()"); + if (!parameters.isEmpty()) { + return new ByteArrayInputStream( + HttpConstants.getContentBytes( + generateRequestBody(parameters), getRequestCharSet())); } + else { + return super.getRequestBody(); + } + } - repeatCount++; - - InputStream instream = this.requestBody; - OutputStream outstream = conn.getRequestOutputStream(); + /** + * Gets the request body string as it would be if it was executed. + * + * @return the request body as a string + * + * @throws IOException when i/o errors occur reading the request + * + * @since 2.0 + */ - if (this.requestContentLength == CONTENT_LENGTH_CHUNKED) { - outstream = new ChunkedOutputStream(outstream); + public String getRequestBodyAsString() throws IOException { + log.trace("enter PostMethod.getRequestBody()"); + if (!parameters.isEmpty()) { + return generateRequestBody(parameters); } - if (this.requestContentLength >= 0) { - // don't need a watcher here - we're reading from something local, - // not server-side. - instream = new ContentLengthInputStream(instream, this.requestContentLength); + else { + return super.getRequestBodyAsString(); } + } - byte[] tmp = new byte[4096]; - int total = 0; - int i = 0; - while ((i = instream.read(tmp)) >= 0) { - outstream.write(tmp, 0, i); - total += i; - } - if (outstream instanceof ChunkedOutputStream) { - ((ChunkedOutputStream) outstream).writeClosingChunk(); - } - if ((this.requestContentLength > 0) && (total < this.requestContentLength)) { - throw new IOException("Unexpected end of input stream after " - + total + " bytes (expected " - + this.requestContentLength + " bytes)"); - } + /** + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to return the length of the request body. + * + * @return number of bytes in the request body + * + * @since 2.0 + */ + protected int getRequestContentLength() { + log.trace("enter PostMethod.getRequestContentLength()"); - if (buffer != null) { - //restore buffered content for repeated requests - requestBody = new ByteArrayInputStream(buffer.toByteArray()); + if (!hasRequestBody()) { + super.setRequestBody(generateRequestBody(parameters)); + bufferContent(); } - - return true; + return super.getRequestContentLength(); } - /** - * Encode the list of parameters into a query stream. - * - * @param params the list of query name and value + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to also add <tt>Content-Type</tt> header when appropriate. * - * @return the query stream + * @param state the client state + * @param conn the {@link HttpConnection} the headers will eventually be + * written to + * @throws IOException when an error occurs writing the request + * @throws HttpException when a HTTP protocol error occurs * - * @since 1.0 + * @since 2.0 */ - protected InputStream generateRequestBody(List params) { - LOG.trace("enter PostMethod.generateRequestBody(List)"); - String body = generateRequestBodyAsString(params); + + protected void addRequestHeaders(HttpState state, HttpConnection conn) + throws IOException, HttpException { + super.addRequestHeaders(state, conn); - return new ByteArrayInputStream( - HttpConstants.getContentBytes(body, getRequestCharSet())); + if (!parameters.isEmpty()) { + //there are some parameters, so set the contentType header + setRequestHeader(CONTENT_TYPE); + } } - - // ------------------------------------------------------------Class Methods - /** - * Encode the list of parameters into a query string. + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to write request parameters as the request body. The input stream will + * be truncated after the specified content length. + * + * @param state the client state + * @param conn the connection to write to * - * @param params the list of query name and value + * @return always returns true * - * @return the query string + * @throws IOException if the stream ends before the specified content + * length. <p> + * @throws HttpException when a protocol error occurs or state is invalid * * @since 2.0 */ - protected static String generateRequestBodyAsString(List params) { - LOG.trace("enter PostMethod.generateRequestBodyAsString(List)"); - Iterator it = params.iterator(); - StringBuffer buff = new StringBuffer(); - - while (it.hasNext()) { - NameValuePair parameter = (NameValuePair) it.next(); + protected boolean writeRequestBody(HttpState state, HttpConnection conn) + throws IOException, HttpException { + log.trace( + "enter PostMethod.writeRequestBody(HttpState, HttpConnection)"); - String queryName = null; - try { - queryName = URIUtil.encodeWithinQuery(parameter.getName()); - } catch (URIException urie) { - LOG.error("encoding error within query name", urie); - queryName = parameter.getName(); - } - buff.append(queryName).append("="); - String queryValue = null; - try { - queryValue = URIUtil.encodeWithinQuery(parameter.getValue()); - } catch (URIException urie) { - LOG.error("encoding error within query value", urie); - queryValue = parameter.getValue(); - } - buff.append(queryValue); - if (it.hasNext()) { - buff.append("&"); - } + if (!hasRequestBody()) { + super.setRequestBody(generateRequestBody(parameters)); } - return buff.toString(); + return super.writeRequestBody(state, conn); } /** - * Buffers the request body and calculates the content length. If the - * method was called earlier it returns immediately. + * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} + * to clear my request body. * * @since 1.0 */ - private void bufferContent() { - LOG.trace("enter PostMethod.bufferContent()"); - - if (buffer != null) { - return; - } - - try { - buffer = new ByteArrayOutputStream(); - - byte[] data = new byte[10000]; - int l = requestBody.read(data); - int total = 0; - - while (l > 0) { - buffer.write(data, 0, l); - total += l; - l = requestBody.read(data); - } - - requestBody = new ByteArrayInputStream(buffer.toByteArray()); - requestContentLength = total; - } catch (IOException e) { - requestBody = null; - requestContentLength = 0; - } + public void recycle() { + log.trace("enter PostMethod.recycle()"); + super.recycle(); + this.parameters.clear(); } + } Index: java/org/apache/commons/httpclient/methods/PutMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v retrieving revision 1.20 diff -u -r1.20 PutMethod.java --- java/org/apache/commons/httpclient/methods/PutMethod.java 28 Jan 2003 22:25:28 -0000 1.20 +++ java/org/apache/commons/httpclient/methods/PutMethod.java 29 Jan 2003 16:12:02 +-0000 @@ -1,8 +1,7 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v 1.20 2003/01/28 22:25:28 jsdever Exp $ - * $Revision: 1.20 $ - * $Date: 2003/01/28 22:25:28 $ - * + * $Header: +/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PutMethod.java,v + 1.19 2003/01/23 22:48:09 jsdever Exp $ + * $Revision: 1.19 $ + * $Date: 2003/01/23 22:48:09 $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -63,34 +62,15 @@ package org.apache.commons.httpclient.methods; -import org.apache.commons.httpclient.HttpConstants; -import org.apache.commons.httpclient.HttpConnection; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpMethodBase; -import org.apache.commons.httpclient.HttpState; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; - - /** * PUT Method. * * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> - * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> * * @since 1.0 */ -public class PutMethod extends HttpMethodBase { +public class PutMethod extends EntityEnclosingMethod { // ----------------------------------------------------------- Constructors @@ -118,28 +98,6 @@ setFollowRedirects(false); } - - // ------------------------------------------------------- Instance Methods - - - /** - * Request body content to be sent. - */ - private byte[] data = null; - - - /** - * Request body content to be sent. - */ - private File file = null; - - - /** - * Request body content to be sent. - */ - private URL url = null; - - // --------------------------------------------------------- Public Methods /** @@ -151,187 +109,4 @@ public String getName() { return "PUT"; } - - /** - * Set my request body content to the contents of a file. - * - * @param file The file - * @throws IOException if an IO problem occurs - * @since 2.0 - */ - public void setRequestBody(File file) throws IOException { - checkNotUsed(); - this.file = file; - } - - /** - * Set my request body content to the resource at the specified URL. - * - * @param url The URL - * @throws IOException If an IO problem occurs. - * @since 2.0 - */ - public void setRequestBody(URL url) throws IOException { - checkNotUsed(); - this.url = url; - } - - /** - * Set my request body content to the contents of a byte array. - * - * @param bodydata The new content. - * @since 2.0 - */ - public void setRequestBody(byte[] bodydata) { - checkNotUsed(); - this.data = bodydata; - } - - /** - * Set my request body content to the contents of a string. - * - * @param bodydata The new content - * @since 2.0 - */ - public void setRequestBody(String bodydata) { - checkNotUsed(); - setRequestBody(HttpConstants.getContentBytes(bodydata, getRequestCharSet())); - } - - /** - * Set my request body content to the contents of an input stream. The - * contents will be buffered into memory. To upload large entities, it is - * recommended to first buffer the data into a temporary file, and then send - * that file. - * - * @param is The input stream. - * @throws IOException If an IO problem occurs - * @since 2.0 - */ - public void setRequestBody(InputStream is) throws IOException { - LOG.trace("enter PutMethod.setRequestBody(InputStream)"); - - checkNotUsed(); - byte[] buffer = new byte[4096]; - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int nb = 0; - while (true) { - nb = is.read(buffer); - if (nb == -1) { - break; - } - os.write(buffer, 0, nb); - } - data = os.toByteArray(); - } - - - // ------------------------------------------------- HttpMethodBase Methods - - /** - * Override the method of {@link HttpMethodBase} to set the <tt>Expect</tt> - * header if it has not already been set, in addition to the "standard" set - * of headers. - * - * @param state The state. - * @param conn The connection. - * @throws IOException If an IO problem occurs - * @throws HttpException Never. - * @since 2.0 - */ - protected void addRequestHeaders(HttpState state, HttpConnection conn) - throws IOException, HttpException { - // TODO: Determine why this method is declared to throw HttpException - // since it never actually does throw it. - LOG.trace("enter PutMethod.addRequestHeaders(HttpState, HttpConnection)"); - - super.addRequestHeaders(state, conn); - // Send expectation header - if (isHttp11() && null == getRequestHeader("expect")) { - setRequestHeader("Expect", "100-continue"); - } - } - - /** - * Override the method of {@link HttpMethodBase} to not send any data until - * the <tt>100 Continue</tt> status has not be read. - * - * @param state The state - * @param conn The connection - * @return true if the data was written. - * @throws IOException If an IO problem occurs - * @throws HttpException This doesn't ever seem to be thrown. - * @since 2.0 - */ - protected boolean writeRequestBody(HttpState state, HttpConnection conn) - throws IOException, HttpException { - LOG.trace("enter PutMethod.writeRequestBody(HttpState, HttpConnection)"); - if (getStatusLine() == null) { - return false; - } - if (null != getRequestHeader("expect") - && getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) { - return false; - } - OutputStream out = conn.getRequestOutputStream((isHttp11() - && (null == getRequestHeader("Content-Length")))); - - InputStream inputStream = null; - if (file != null && file.exists()) { - inputStream = new FileInputStream(file); - } else if (url != null) { - inputStream = url.openConnection().getInputStream(); - } else if (data != null) { - inputStream = new ByteArrayInputStream(data); - } else { - return true; - } - - byte[] buffer = new byte[4096]; - int nb = 0; - while (true) { - nb = inputStream.read(buffer); - if (nb == -1) { - break; - } - out.write(buffer, 0, nb); - } - out.flush(); - return true; - } - - /** - * Override the method of {@link HttpMethodBase} - * to return the appropriate content length. - * - * @return the content length - * @since 2.0 - */ - protected int getRequestContentLength() { - LOG.trace("enter PutMethod.getRequestContentLength()"); - - if (null != data) { - return data.length; - } else if (null != file && file.exists()) { - return (int) (file.length()); - } else if (url != null) { - return -1; - } else { - return 0; - } - } - - /** - * - * @since 1.0 - */ - public void recycle() { - super.recycle(); - data = null; - url = null; - file = null; - } - - /** Log object for this class. */ - private static final Log LOG = LogFactory.getLog(PutMethod.class); }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]