/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.39 2004/07/03 14:27:03 olegk Exp $
* $Revision: 480424 $
* $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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/>.
*
*/
package org.apache.commons.httpclient.methods;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.commons.httpclient.ChunkedOutputStream;
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.HttpVersion;
import org.apache.commons.httpclient.ProtocolException;
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]">Oleg Kalnichevski</a>
* @author <a href="mailto:[email protected]">Jeff Dever</a>
*
* @since 2.0beta1
* @version $Revision: 480424 $
*/
public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
// ----------------------------------------- Static variables/initializers
/**
* The content length will be calculated automatically. This implies
* buffering of the content.
* @deprecated Use {@link InputStreamRequestEntity#CONTENT_LENGTH_AUTO}.
*/
public static final long CONTENT_LENGTH_AUTO = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
/**
* The request will use chunked transfer encoding. Content length is not
* calculated and the content is not buffered.<br>
* @deprecated Use {@link #setContentChunked(boolean)}.
*/
public static final long CONTENT_LENGTH_CHUNKED = -1;
/** LOG object for this class. */
private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
/** The unbuffered request body, if any. */
private InputStream requestStream = null;
/** The request body as string, if any. */
private String requestString = null;
private RequestEntity requestEntity;
/** 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>.
*
* @deprecated
*/
private long requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
private boolean chunked = false;
// ----------------------------------------------------------- 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);
}
/**
* Returns <tt>true</tt> if there is a request body to be sent.
*
* <P>This method must be overridden by sub-classes that implement
* alternative request content input methods
* </p>
*
* @return boolean
*
* @since 2.0beta1
*/
protected boolean hasRequestContent() {
LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
return (this.requestEntity != null)
|| (this.requestStream != null)
|| (this.requestString != null);
}
/**
* Clears the request body.
*
* <p>This method must be overridden by sub-classes that implement
* alternative request content input methods.</p>
*
* @since 2.0beta1
*/
protected void clearRequestBody() {
LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
this.requestStream = null;
this.requestString = null;
this.requestEntity = null;
}
/**
* Generates the request body.
*
* <p>This method must be overridden by sub-classes that implement
* alternative request content input methods.</p>
*
* @return request body as an array of bytes. If the request content
* has not been set, returns <tt>null</tt>.
*
* @since 2.0beta1
*/
protected byte[] generateRequestBody() {
LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
return null;
}
protected RequestEntity generateRequestEntity() {
byte[] requestBody = generateRequestBody();
if (requestBody != null) {
// use the request body, if it exists.
// this is just for backwards compatability
this.requestEntity = new ByteArrayRequestEntity(requestBody);
} else if (this.requestStream != null) {
this.requestEntity = new InputStreamRequestEntity(
requestStream,
requestContentLength);
this.requestStream = null;
} else if (this.requestString != null) {
String charset = getRequestCharSet();
try {
this.requestEntity = new StringRequestEntity(
requestString, null, charset);
} catch (UnsupportedEncodingException e) {
if (LOG.isWarnEnabled()) {
LOG.warn(charset + " not supported");
}
try {
this.requestEntity = new StringRequestEntity(
requestString, null, null);
} catch (UnsupportedEncodingException ignore) {
}
}
}
return this.requestEntity;
}
/**
* Entity enclosing requests cannot be redirected without user intervention
* according to RFC 2616.
*
* @return <code>false</code>.
*
* @since 2.0
*/
public boolean getFollowRedirects() {
return false;
}
/**
* Entity enclosing requests cannot be redirected without user intervention
* according to RFC 2616.
*
* @param followRedirects must always be <code>false</code>
*/
public void setFollowRedirects(boolean followRedirects) {
if (followRedirects == true) {
throw new IllegalArgumentException("Entity enclosing requests cannot be redirected without user intervention");
}
super.setFollowRedirects(false);
}
/**
* 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.
*
* @deprecated Use {@link #setContentChunked(boolean)} or
* {@link #setRequestEntity(RequestEntity)}
*/
public void setRequestContentLength(int length) {
LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
this.requestContentLength = length;
}
/**
* Returns the request's charset. The charset is parsed from the request entity's
* content type, unless the content type header has been set manually.
*
* @see RequestEntity#getContentType()
*
* @since 3.0
*/
public String getRequestCharSet() {
if (getRequestHeader("Content-Type") == null) {
// check the content type from request entity
// We can't call getRequestEntity() since it will probably call
// this method.
if (this.requestEntity != null) {
return getContentCharSet(
new Header("Content-Type", requestEntity.getContentType()));
} else {
return super.getRequestCharSet();
}
} else {
return super.getRequestCharSet();
}
}
/**
* 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.
*
* @deprecated Use {@link #setContentChunked(boolean)} or
* {@link #setRequestEntity(RequestEntity)}
*/
public void setRequestContentLength(long length) {
LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
this.requestContentLength = length;
}
/**
* Sets whether or not the content should be chunked.
*
* @param chunked <code>true</code> if the content should be chunked
*
* @since 3.0
*/
public void setContentChunked(boolean chunked) {
this.chunked = chunked;
}
/**
* Returns the length of the request body.
*
* @return number of bytes in the request body
*/
protected long getRequestContentLength() {
LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
if (!hasRequestContent()) {
return 0;
}
if (this.chunked) {
return -1;
}
if (this.requestEntity == null) {
this.requestEntity = generateRequestEntity();
}
return (this.requestEntity == null) ? 0 : this.requestEntity.getContentLength();
}
/**
* Populates the request headers map to with additional
* {@link org.apache.commons.httpclient.Header headers} to be submitted to
* the given {@link HttpConnection}.
*
* <p>
* This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
* headers.
* </p>
*
* <p>
* Subclasses may want to override this method to to add additional
* headers, and may choose to invoke this implementation (via
* <tt>super</tt>) to add the "standard" headers.
* </p>
*
* @param state the {@link HttpState state} information associated with this method
* @param conn the {@link HttpConnection connection} used to execute
* this HTTP method
*
* @throws IOException if an I/O (transport) error occurs. Some transport exceptions
* can be recovered from.
* @throws HttpException if a protocol exception occurs. Usually protocol exceptions
* cannot be recovered from.
*
* @see #writeRequestHeaders
*
* @since 3.0
*/
protected void addRequestHeaders(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
+ "HttpConnection)");
super.addRequestHeaders(state, conn);
addContentLengthRequestHeader(state, conn);
// only use the content type of the request entity if it has not already been
// set manually
if (getRequestHeader("Content-Type") == null) {
RequestEntity requestEntity = getRequestEntity();
if (requestEntity != null && requestEntity.getContentType() != null) {
setRequestHeader("Content-Type", requestEntity.getContentType());
}
}
}
/**
* Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
* request header, as long as no <tt>Content-Length</tt> request header
* already exists.
*
* @param state current state of http requests
* @param conn the connection to use for I/O
*
* @throws IOException when errors occur reading or writing to/from the
* connection
* @throws HttpException when a recoverable error occurs
*/
protected void addContentLengthRequestHeader(HttpState state,
HttpConnection conn)
throws IOException, HttpException {
LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
+ "HttpState, HttpConnection)");
if ((getRequestHeader("content-length") == null)
&& (getRequestHeader("Transfer-Encoding") == null)) {
long len = getRequestContentLength();
if (len < 0) {
if (getEffectiveVersion().greaterEquals(HttpVersion.HTTP_1_1)) {
addRequestHeader("Transfer-Encoding", "chunked");
} else {
throw new ProtocolException(getEffectiveVersion() +
" does not support chunk encoding");
}
} else {
addRequestHeader("Content-Length", String.valueOf(len));
}
}
}
/**
* Sets the request body to be the specified inputstream.
*
* @param body Request body content as {@link java.io.InputStream}
*
* @deprecated use {@link #setRequestEntity(RequestEntity)}
*/
public void setRequestBody(InputStream body) {
LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
clearRequestBody();
this.requestStream = body;
}
/**
* Sets the request body to be the specified string.
* The string will be submitted, using the encoding
* specified in the Content-Type request header.<br>
* Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
* Would use the UTF-8 encoding.
* If no charset is specified, the
* {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
* content encoding is used (ISO-8859-1).
*
* @param body Request body content as a string
*
* @deprecated use {@link #setRequestEntity(RequestEntity)}
*/
public void setRequestBody(String body) {
LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
clearRequestBody();
this.requestString = body;
}
/**
* Writes the request body to the given {@link HttpConnection connection}.
*
* @param state the {@link HttpState state} information associated with this method
* @param conn the {@link HttpConnection connection} used to execute
* this HTTP method
*
* @return <tt>true</tt>
*
* @throws IOException if an I/O (transport) error occurs. Some transport exceptions
* can be recovered from.
* @throws HttpException if a protocol exception occurs. Usually protocol exceptions
* cannot be recovered from.
*/
protected boolean writeRequestBody(HttpState state, HttpConnection conn)
throws IOException, HttpException {
LOG.trace(
"enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
if (!hasRequestContent()) {
LOG.debug("Request body has not been specified");
return true;
}
if (this.requestEntity == null) {
this.requestEntity = generateRequestEntity();
}
if (requestEntity == null) {
LOG.debug("Request body is empty");
return true;
}
long contentLength = getRequestContentLength();
if ((this.repeatCount > 0) && !requestEntity.isRepeatable()) {
throw new ProtocolException(
"Unbuffered entity enclosing request can not be repeated.");
}
this.repeatCount++;
OutputStream outstream = conn.getRequestOutputStream();
if (contentLength < 0) {
outstream = new ChunkedOutputStream(outstream);
}
requestEntity.writeRequest(outstream);
// This is hardly the most elegant solution to closing chunked stream
if (outstream instanceof ChunkedOutputStream) {
((ChunkedOutputStream) outstream).finish();
}
outstream.flush();
LOG.debug("Request body sent");
return true;
}
/**
* Recycles the HTTP method so that it can be used again.
* Note that all of the instance variables will be reset
* once this method has been called. This method will also
* release the connection being used by this HTTP method.
*
* @see #releaseConnection()
*
* @deprecated no longer supported and will be removed in the future
* version of HttpClient
*/
public void recycle() {
LOG.trace("enter EntityEnclosingMethod.recycle()");
clearRequestBody();
this.requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
this.repeatCount = 0;
this.chunked = false;
super.recycle();
}
/**
* @return Returns the requestEntity.
*
* @since 3.0
*/
public RequestEntity getRequestEntity() {
return generateRequestEntity();
}
/**
* @param requestEntity The requestEntity to set.
*
* @since 3.0
*/
public void setRequestEntity(RequestEntity requestEntity) {
clearRequestBody();
this.requestEntity = requestEntity;
}
}
|