Java Source Code: org.apache.commons.httpclient.methods.EntityEnclosingMethod


   1: /*
   2:  * $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 $
   3:  * $Revision: 480424 $
   4:  * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
   5:  *
   6:  * ====================================================================
   7:  *
   8:  *  Licensed to the Apache Software Foundation (ASF) under one or more
   9:  *  contributor license agreements.  See the NOTICE file distributed with
  10:  *  this work for additional information regarding copyright ownership.
  11:  *  The ASF licenses this file to You under the Apache License, Version 2.0
  12:  *  (the "License"); you may not use this file except in compliance with
  13:  *  the License.  You may obtain a copy of the License at
  14:  *
  15:  *      http://www.apache.org/licenses/LICENSE-2.0
  16:  *
  17:  *  Unless required by applicable law or agreed to in writing, software
  18:  *  distributed under the License is distributed on an "AS IS" BASIS,
  19:  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20:  *  See the License for the specific language governing permissions and
  21:  *  limitations under the License.
  22:  * ====================================================================
  23:  *
  24:  * This software consists of voluntary contributions made by many
  25:  * individuals on behalf of the Apache Software Foundation.  For more
  26:  * information on the Apache Software Foundation, please see
  27:  * <http://www.apache.org/>.
  28:  *
  29:  */
  30: 
  31: package org.apache.commons.httpclient.methods;
  32: 
  33: import java.io.IOException;
  34: import java.io.InputStream;
  35: import java.io.OutputStream;
  36: import java.io.UnsupportedEncodingException;
  37: 
  38: import org.apache.commons.httpclient.ChunkedOutputStream;
  39: import org.apache.commons.httpclient.Header;
  40: import org.apache.commons.httpclient.HttpConnection;
  41: import org.apache.commons.httpclient.HttpException;
  42: import org.apache.commons.httpclient.HttpState;
  43: import org.apache.commons.httpclient.HttpVersion;
  44: import org.apache.commons.httpclient.ProtocolException;
  45: import org.apache.commons.logging.Log;
  46: import org.apache.commons.logging.LogFactory;
  47: 
  48: /**
  49:  * This abstract class serves as a foundation for all HTTP methods 
  50:  * that can enclose an entity within requests 
  51:  *
  52:  * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
  53:  * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
  54:  *
  55:  * @since 2.0beta1
  56:  * @version $Revision: 480424 $
  57:  */
  58:	  public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
  59:
  60:    // ----------------------------------------- Static variables/initializers
  61:
  62:    /**
  63:     * The content length will be calculated automatically. This implies
  64:     * buffering of the content.
  65:     * @deprecated Use {@link InputStreamRequestEntity#CONTENT_LENGTH_AUTO}.
  66:     */
  67:    public static final long CONTENT_LENGTH_AUTO = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
  68:
  69:    /**
  70:     * The request will use chunked transfer encoding. Content length is not
  71:     * calculated and the content is not buffered.<br>
  72:     * @deprecated Use {@link #setContentChunked(boolean)}.
  73:     */
  74:    public static final long CONTENT_LENGTH_CHUNKED = -1;
  75:
  76:    /** LOG object for this class. */
  77:    private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
  78:
  79:    /** The unbuffered request body, if any. */
  80:    private InputStream requestStream = null;
  81:
  82:    /** The request body as string, if any. */
  83:    private String requestString = null;
  84:
  85:    private RequestEntity requestEntity;
  86:    
  87:    /** Counts how often the request was sent to the server. */
  88:    private int repeatCount = 0;
  89:
  90:    /** The content length of the <code>requestBodyStream</code> or one of
  91:     *  <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
  92:     * 
  93:     * @deprecated
  94:     */
  95:    private long requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
  96:    
  97:    private boolean chunked = false;
  98:
  99:    // ----------------------------------------------------------- Constructors
 100:
 101:    /**
 102:     * No-arg constructor.
 103:     *
 104:     * @since 2.0
 105:     */
 106:	      public EntityEnclosingMethod() {
 107:        super();
 108:        setFollowRedirects(false);
 109:    }
 110:
 111:    /**
 112:     * Constructor specifying a URI.
 113:     *
 114:     * @param uri either an absolute or relative URI
 115:     *
 116:     * @since 2.0
 117:     */
 118:	      public EntityEnclosingMethod(String uri) {
 119:        super(uri);
 120:        setFollowRedirects(false);
 121:    }
 122:
 123:    /**
 124:     * Returns <tt>true</tt> if there is a request body to be sent.
 125:     * 
 126:     * <P>This method must be overridden by sub-classes that implement
 127:     * alternative request content input methods
 128:     * </p>
 129:     * 
 130:     * @return boolean
 131:     * 
 132:     * @since 2.0beta1
 133:     */
 134:	      protected boolean hasRequestContent() {
 135:        LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
 136:        return (this.requestEntity != null) 
 137:            || (this.requestStream != null) 
 138:            || (this.requestString != null);
 139:    }
 140:
 141:    /**
 142:     * Clears the request body.
 143:     * 
 144:     * <p>This method must be overridden by sub-classes that implement
 145:     * alternative request content input methods.</p>
 146:     * 
 147:     * @since 2.0beta1
 148:     */
 149:	      protected void clearRequestBody() {
 150:        LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
 151:        this.requestStream = null;
 152:        this.requestString = null;
 153:        this.requestEntity = null;
 154:    }
 155:
 156:    /**
 157:     * Generates the request body.   
 158:     * 
 159:     * <p>This method must be overridden by sub-classes that implement
 160:     * alternative request content input methods.</p>
 161:     * 
 162:     * @return request body as an array of bytes. If the request content 
 163:     *          has not been set, returns <tt>null</tt>.
 164:     * 
 165:     * @since 2.0beta1
 166:     */
 167:	      protected byte[] generateRequestBody() {
 168:        LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
 169:        return null;
 170:    }
 171:
 172:	      protected RequestEntity generateRequestEntity() {
 173:        
 174:        byte[] requestBody = generateRequestBody();
 175:	          if (requestBody != null) {
 176:            // use the request body, if it exists.
 177:            // this is just for backwards compatability
 178:            this.requestEntity = new ByteArrayRequestEntity(requestBody);
 179:        } else if (this.requestStream != null) {
 180:            this.requestEntity = new InputStreamRequestEntity(
 181:                requestStream, 
 182:                requestContentLength);
 183:            this.requestStream = null;
 184:        } else if (this.requestString != null) {
 185:            String charset = getRequestCharSet(); 
 186:	              try {
 187:                this.requestEntity = new StringRequestEntity(
 188:                        requestString, null, charset);
 189:            } catch (UnsupportedEncodingException e) {
 190:	                  if (LOG.isWarnEnabled()) {
 191:                    LOG.warn(charset + " not supported");
 192:                }
 193:	                  try {
 194:                    this.requestEntity = new StringRequestEntity(
 195:                            requestString, null, null);
 196:                } catch (UnsupportedEncodingException ignore) {
 197:                }
 198:            }
 199:        }
 200:
 201:        return this.requestEntity;
 202:    }
 203:    
 204:    /**
 205:     * Entity enclosing requests cannot be redirected without user intervention
 206:     * according to RFC 2616.
 207:     *
 208:     * @return <code>false</code>.
 209:     *
 210:     * @since 2.0
 211:     */
 212:	      public boolean getFollowRedirects() {
 213:        return false;
 214:    }
 215:
 216:
 217:    /**
 218:     * Entity enclosing requests cannot be redirected without user intervention 
 219:     * according to RFC 2616.
 220:     *
 221:     * @param followRedirects must always be <code>false</code>
 222:     */
 223:	      public void setFollowRedirects(boolean followRedirects) {
 224:	          if (followRedirects == true) {
 225:            throw new IllegalArgumentException("Entity enclosing requests cannot be redirected without user intervention");
 226:        }
 227:        super.setFollowRedirects(false);
 228:    }
 229:
 230:    /**
 231:     * Sets length information about the request body.
 232:     *
 233:     * <p>
 234:     * Note: If you specify a content length the request is unbuffered. This
 235:     * prevents redirection and automatic retry if a request fails the first
 236:     * time. This means that the HttpClient can not perform authorization
 237:     * automatically but will throw an Exception. You will have to set the
 238:     * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
 239:     * </p>
 240:     *
 241:     * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
 242:     *        CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
 243:     *        is specified the content will not be buffered internally and the
 244:     *        Content-Length header of the request will be used. In this case
 245:     *        the user is responsible to supply the correct content length.
 246:     *        If CONTENT_LENGTH_AUTO is specified the request will be buffered
 247:     *        before it is sent over the network.
 248:     * 
 249:     * @deprecated Use {@link #setContentChunked(boolean)} or 
 250:     * {@link #setRequestEntity(RequestEntity)}
 251:     */
 252:	      public void setRequestContentLength(int length) {
 253:        LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
 254:        this.requestContentLength = length;
 255:    }
 256:
 257:    /**
 258:     * Returns the request's charset.  The charset is parsed from the request entity's 
 259:     * content type, unless the content type header has been set manually. 
 260:     * 
 261:     * @see RequestEntity#getContentType()
 262:     * 
 263:     * @since 3.0
 264:     */
 265:	      public String getRequestCharSet() {
 266:	          if (getRequestHeader("Content-Type") == null) {
 267:            // check the content type from request entity
 268:            // We can't call getRequestEntity() since it will probably call
 269:            // this method.
 270:	              if (this.requestEntity != null) {
 271:                return getContentCharSet(
 272:                    new Header("Content-Type", requestEntity.getContentType()));
 273:            } else {
 274:                return super.getRequestCharSet();
 275:            }
 276:        } else {
 277:            return super.getRequestCharSet();
 278:        }
 279:    }
 280:
 281:    /**
 282:     * Sets length information about the request body.
 283:     *
 284:     * <p>
 285:     * Note: If you specify a content length the request is unbuffered. This
 286:     * prevents redirection and automatic retry if a request fails the first
 287:     * time. This means that the HttpClient can not perform authorization
 288:     * automatically but will throw an Exception. You will have to set the
 289:     * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
 290:     * </p>
 291:     *
 292:     * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
 293:     *        CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
 294:     *        is specified the content will not be buffered internally and the
 295:     *        Content-Length header of the request will be used. In this case
 296:     *        the user is responsible to supply the correct content length.
 297:     *        If CONTENT_LENGTH_AUTO is specified the request will be buffered
 298:     *        before it is sent over the network.
 299:     * 
 300:     * @deprecated Use {@link #setContentChunked(boolean)} or 
 301:     * {@link #setRequestEntity(RequestEntity)}
 302:     */
 303:	      public void setRequestContentLength(long length) {
 304:        LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
 305:        this.requestContentLength = length;
 306:    }
 307:
 308:    /**
 309:     * Sets whether or not the content should be chunked.
 310:     * 
 311:     * @param chunked <code>true</code> if the content should be chunked
 312:     * 
 313:     * @since 3.0
 314:     */
 315:	      public void setContentChunked(boolean chunked) {
 316:        this.chunked = chunked;
 317:    }
 318:    
 319:    /**
 320:     * Returns the length of the request body.
 321:     *
 322:     * @return number of bytes in the request body
 323:     */
 324:	      protected long getRequestContentLength() {
 325:        LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
 326:
 327:	          if (!hasRequestContent()) {
 328:            return 0;
 329:        }
 330:	          if (this.chunked) {
 331:            return -1;
 332:        }
 333:	          if (this.requestEntity == null) {
 334:            this.requestEntity = generateRequestEntity(); 
 335:        }
 336:        return (this.requestEntity == null) ? 0 : this.requestEntity.getContentLength();
 337:    }
 338:
 339:    /**
 340:     * Populates the request headers map to with additional 
 341:     * {@link org.apache.commons.httpclient.Header headers} to be submitted to 
 342:     * the given {@link HttpConnection}.
 343:     *
 344:     * <p>
 345:     * This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
 346:     * headers.
 347:     * </p>
 348:     *
 349:     * <p>
 350:     * Subclasses may want to override this method to to add additional
 351:     * headers, and may choose to invoke this implementation (via
 352:     * <tt>super</tt>) to add the "standard" headers.
 353:     * </p>
 354:     *
 355:     * @param state the {@link HttpState state} information associated with this method
 356:     * @param conn the {@link HttpConnection connection} used to execute
 357:     *        this HTTP method
 358:     *
 359:     * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
 360:     *                     can be recovered from.
 361:     * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
 362:     *                    cannot be recovered from.
 363:     *
 364:     * @see #writeRequestHeaders
 365:     * 
 366:     * @since 3.0
 367:     */
 368:    protected void addRequestHeaders(HttpState state, HttpConnection conn)
 369:	      throws IOException, HttpException {
 370:        LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
 371:            + "HttpConnection)");
 372:
 373:        super.addRequestHeaders(state, conn);
 374:        addContentLengthRequestHeader(state, conn);
 375:
 376:        // only use the content type of the request entity if it has not already been
 377:        // set manually
 378:	          if (getRequestHeader("Content-Type") == null) {
 379:            RequestEntity requestEntity = getRequestEntity();
 380:	              if (requestEntity != null && requestEntity.getContentType() != null) {
 381:                setRequestHeader("Content-Type", requestEntity.getContentType());
 382:            }
 383:        }
 384:    }
 385:    
 386:    /**
 387:     * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
 388:     * request header, as long as no <tt>Content-Length</tt> request header
 389:     * already exists.
 390:     *
 391:     * @param state current state of http requests
 392:     * @param conn the connection to use for I/O
 393:     *
 394:     * @throws IOException when errors occur reading or writing to/from the
 395:     *         connection
 396:     * @throws HttpException when a recoverable error occurs
 397:     */
 398:    protected void addContentLengthRequestHeader(HttpState state,
 399:                                                 HttpConnection conn)
 400:	      throws IOException, HttpException {
 401:        LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
 402:                  + "HttpState, HttpConnection)");
 403:
 404:        if ((getRequestHeader("content-length") == null) 
 405:	              && (getRequestHeader("Transfer-Encoding") == null)) {
 406:            long len = getRequestContentLength();
 407:	              if (len < 0) {
 408:	                  if (getEffectiveVersion().greaterEquals(HttpVersion.HTTP_1_1)) {
 409:                    addRequestHeader("Transfer-Encoding", "chunked");
 410:                } else {
 411:                    throw new ProtocolException(getEffectiveVersion() + 
 412:                        " does not support chunk encoding");
 413:                }
 414:            } else {
 415:                addRequestHeader("Content-Length", String.valueOf(len));
 416:            }
 417:        }
 418:    }
 419:
 420:    /**
 421:     * Sets the request body to be the specified inputstream.
 422:     *
 423:     * @param body Request body content as {@link java.io.InputStream}
 424:     * 
 425:     * @deprecated use {@link #setRequestEntity(RequestEntity)}
 426:     */
 427:	      public void setRequestBody(InputStream body) {
 428:        LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
 429:        clearRequestBody();
 430:        this.requestStream = body;
 431:    }
 432:
 433:    /**
 434:     * Sets the request body to be the specified string.
 435:     * The string will be submitted, using the encoding
 436:     * specified in the Content-Type request header.<br>
 437:     * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
 438:     * Would use the UTF-8 encoding.
 439:     * If no charset is specified, the 
 440:     * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
 441:     * content encoding is used (ISO-8859-1).
 442:     *
 443:     * @param body Request body content as a string
 444:     * 
 445:     * @deprecated use {@link #setRequestEntity(RequestEntity)}
 446:     */
 447:	      public void setRequestBody(String body) {
 448:        LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
 449:        clearRequestBody();
 450:        this.requestString = body;
 451:    }
 452:
 453:    /**
 454:     * Writes the request body to the given {@link HttpConnection connection}.
 455:     *
 456:     * @param state the {@link HttpState state} information associated with this method
 457:     * @param conn the {@link HttpConnection connection} used to execute
 458:     *        this HTTP method
 459:     *
 460:     * @return <tt>true</tt>
 461:     *
 462:     * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
 463:     *                     can be recovered from.
 464:     * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
 465:     *                    cannot be recovered from.
 466:     */
 467:    protected boolean writeRequestBody(HttpState state, HttpConnection conn)
 468:	      throws IOException, HttpException {
 469:        LOG.trace(
 470:            "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
 471:        
 472:	          if (!hasRequestContent()) {
 473:            LOG.debug("Request body has not been specified");
 474:            return true;
 475:        }
 476:	          if (this.requestEntity == null) {
 477:            this.requestEntity = generateRequestEntity(); 
 478:        }
 479:	          if (requestEntity == null) {
 480:            LOG.debug("Request body is empty");
 481:            return true;
 482:        }
 483:
 484:        long contentLength = getRequestContentLength();
 485:
 486:	          if ((this.repeatCount > 0) && !requestEntity.isRepeatable()) {
 487:            throw new ProtocolException(
 488:                "Unbuffered entity enclosing request can not be repeated.");
 489:        }
 490:
 491:        this.repeatCount++;
 492:
 493:        OutputStream outstream = conn.getRequestOutputStream();
 494:        
 495:	          if (contentLength < 0) {
 496:            outstream = new ChunkedOutputStream(outstream);
 497:        }
 498:        
 499:        requestEntity.writeRequest(outstream);
 500:        
 501:        // This is hardly the most elegant solution to closing chunked stream
 502:	          if (outstream instanceof ChunkedOutputStream) {
 503:            ((ChunkedOutputStream) outstream).finish();
 504:        }
 505:        
 506:        outstream.flush();
 507:        
 508:        LOG.debug("Request body sent");
 509:        return true;
 510:    }
 511:
 512:    /**
 513:     * Recycles the HTTP method so that it can be used again.
 514:     * Note that all of the instance variables will be reset
 515:     * once this method has been called. This method will also
 516:     * release the connection being used by this HTTP method.
 517:     * 
 518:     * @see #releaseConnection()
 519:     * 
 520:     * @deprecated no longer supported and will be removed in the future
 521:     *             version of HttpClient
 522:     */
 523:	      public void recycle() {
 524:        LOG.trace("enter EntityEnclosingMethod.recycle()");
 525:        clearRequestBody();
 526:        this.requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
 527:        this.repeatCount = 0;
 528:        this.chunked = false;
 529:        super.recycle();
 530:    }
 531:
 532:    /**
 533:     * @return Returns the requestEntity.
 534:     * 
 535:     * @since 3.0
 536:     */
 537:	      public RequestEntity getRequestEntity() {
 538:        return generateRequestEntity();
 539:    }
 540:
 541:    /**
 542:     * @param requestEntity The requestEntity to set.
 543:     * 
 544:     * @since 3.0
 545:     */
 546:	      public void setRequestEntity(RequestEntity requestEntity) {
 547:        clearRequestBody();
 548:        this.requestEntity = requestEntity;
 549:    }
 550:
 551:}