View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.18.2.3 2003/12/14 22:41:37 mbecke Exp $ 3 * $Revision: 1.18.2.3 $ 4 * $Date: 2003/12/14 22:41:37 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 * [Additional notices, if required by prior licensing conditions] 61 * 62 */ 63 64 package org.apache.commons.httpclient.methods; 65 66 import java.io.ByteArrayInputStream; 67 import java.io.ByteArrayOutputStream; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.io.OutputStream; 71 72 import org.apache.commons.httpclient.ChunkedOutputStream; 73 import org.apache.commons.httpclient.ContentLengthInputStream; 74 import org.apache.commons.httpclient.HttpConnection; 75 import org.apache.commons.httpclient.HttpConstants; 76 import org.apache.commons.httpclient.HttpException; 77 import org.apache.commons.httpclient.HttpState; 78 import org.apache.commons.logging.Log; 79 import org.apache.commons.logging.LogFactory; 80 81 /*** 82 * This abstract class serves as a foundation for all HTTP methods 83 * that can enclose an entity within requests 84 * 85 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> 86 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> 87 * 88 * @since 2.0beta1 89 * @version $Revision: 1.18.2.3 $ 90 */ 91 public abstract class EntityEnclosingMethod extends ExpectContinueMethod { 92 93 // ----------------------------------------- Static variables/initializers 94 95 /*** 96 * The content length will be calculated automatically. This implies 97 * buffering of the content. 98 */ 99 public static final int CONTENT_LENGTH_AUTO = -2; 100 101 /*** 102 * The request will use chunked transfer encoding. Content length is not 103 * calculated and the content is not buffered.<br> 104 */ 105 public static final int CONTENT_LENGTH_CHUNKED = -1; 106 107 /*** LOG object for this class. */ 108 private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class); 109 110 /*** The buffered request body, if any. */ 111 private byte[] buffer = null; 112 113 /*** The unbuffered request body, if any. */ 114 private InputStream requestStream = null; 115 116 /*** The request body as string, if any. */ 117 private String requestString = null; 118 119 /*** for optimization purpose, the generated request body may be 120 * cached when the method is being executed. 121 */ 122 private byte[] contentCache = null; 123 124 /*** Counts how often the request was sent to the server. */ 125 private int repeatCount = 0; 126 127 /*** The content length of the <code>requestBodyStream</code> or one of 128 * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>. 129 */ 130 private int requestContentLength = CONTENT_LENGTH_AUTO; 131 132 // ----------------------------------------------------------- Constructors 133 134 /*** 135 * No-arg constructor. 136 * 137 * @since 2.0 138 */ 139 public EntityEnclosingMethod() { 140 super(); 141 setFollowRedirects(false); 142 } 143 144 /*** 145 * Constructor specifying a URI. 146 * 147 * @param uri either an absolute or relative URI 148 * 149 * @since 2.0 150 */ 151 public EntityEnclosingMethod(String uri) { 152 super(uri); 153 setFollowRedirects(false); 154 } 155 156 /*** 157 * Constructor specifying a URI and a tempDir. 158 * 159 * @param uri either an absolute or relative URI 160 * @param tempDir directory to store temp files in 161 * 162 * @deprecated the client is responsible for disk I/O 163 * @since 2.0 164 */ 165 public EntityEnclosingMethod(String uri, String tempDir) { 166 super(uri, tempDir); 167 setFollowRedirects(false); 168 } 169 170 /*** 171 * Constructor specifying a URI, tempDir and tempFile. 172 * 173 * @param uri either an absolute or relative URI 174 * @param tempDir directory to store temp files in 175 * @param tempFile file to store temporary data in 176 * 177 * @deprecated the client is responsible for disk I/O 178 * @since 2.0 179 */ 180 public EntityEnclosingMethod(String uri, String tempDir, String tempFile) { 181 super(uri, tempDir, tempFile); 182 setFollowRedirects(false); 183 } 184 185 /*** 186 * Returns <tt>true</tt> if there is a request body to be sent. 187 * 188 * <P>This method must be overridden by sub-classes that implement 189 * alternative request content input methods 190 * </p> 191 * 192 * @return boolean 193 * 194 * @since 2.0beta1 195 */ 196 protected boolean hasRequestContent() { 197 LOG.trace("enter EntityEnclosingMethod.hasRequestContent()"); 198 return (this.buffer != null) 199 || (this.requestStream != null) 200 || (this.requestString != null); 201 } 202 203 /*** 204 * Clears request body. 205 * 206 * <p>This method must be overridden by sub-classes that implement 207 * alternative request content input methods.</p> 208 * 209 * @since 2.0beta1 210 */ 211 protected void clearRequestBody() { 212 LOG.trace("enter EntityEnclosingMethod.clearRequestBody()"); 213 this.requestStream = null; 214 this.requestString = null; 215 this.buffer = null; 216 this.contentCache = null; 217 } 218 219 /*** 220 * Generates request body. 221 * 222 * <p>This method must be overridden by sub-classes that implement 223 * alternative request content input methods.</p> 224 * 225 * @return request body as an array of bytes. If the request content 226 * has not been set, returns <tt>null</tt>. 227 * 228 * @since 2.0beta1 229 */ 230 protected byte[] generateRequestBody() { 231 LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()"); 232 if (this.requestStream != null) { 233 bufferContent(); 234 } 235 236 if (this.buffer != null) { 237 return this.buffer; 238 } else if (this.requestString != null) { 239 return HttpConstants.getContentBytes(this.requestString, getRequestCharSet()); 240 } else { 241 return null; 242 } 243 } 244 245 /*** 246 * Entity enclosing requests cannot be redirected without user intervention 247 * according to RFC 2616. 248 * 249 * @return <code>false</code>. 250 * 251 * @since 2.0 252 */ 253 public boolean getFollowRedirects() { 254 return false; 255 } 256 257 258 /*** 259 * Entity enclosing requests cannot be redirected without user intervention 260 * according to RFC 2616. 261 * 262 * @param followRedirects must always be <code>false</code> 263 */ 264 public void setFollowRedirects(boolean followRedirects) { 265 if (followRedirects == true) { 266 // TODO: EntityEnclosingMethod should inherit from HttpMethodBase rather than GetMethod 267 // Enable exception once the inheritence is fixed 268 //throw new IllegalArgumentException( 269 // "Entity enclosing requests cannot be redirected without user intervention"); 270 } 271 super.setFollowRedirects(false); 272 } 273 274 /*** 275 * Sets length information about the request body. 276 * 277 * <p> 278 * Note: If you specify a content length the request is unbuffered. This 279 * prevents redirection and automatic retry if a request fails the first 280 * time. This means that the HttpClient can not perform authorization 281 * automatically but will throw an Exception. You will have to set the 282 * necessary 'Authorization' or 'Proxy-Authorization' headers manually. 283 * </p> 284 * 285 * @param length size in bytes or any of CONTENT_LENGTH_AUTO, 286 * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED 287 * is specified the content will not be buffered internally and the 288 * Content-Length header of the request will be used. In this case 289 * the user is responsible to supply the correct content length. 290 * If CONTENT_LENGTH_AUTO is specified the request will be buffered 291 * before it is sent over the network. 292 * 293 */ 294 public void setRequestContentLength(int length) { 295 LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)"); 296 this.requestContentLength = length; 297 } 298 299 /*** 300 * Overrides method of {@link org.apache.commons.httpclient.HttpMethodBase} 301 * to return the length of the request body. 302 * 303 * @return number of bytes in the request body 304 */ 305 protected int getRequestContentLength() { 306 LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()"); 307 308 if (!hasRequestContent()) { 309 return 0; 310 } 311 if (this.requestContentLength != CONTENT_LENGTH_AUTO) { 312 return this.requestContentLength; 313 } 314 if (this.contentCache == null) { 315 this.contentCache = generateRequestBody(); 316 } 317 return (this.contentCache == null) ? 0 : this.contentCache.length; 318 } 319 320 /*** 321 * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt> 322 * request header, as long as no <tt>Content-Length</tt> request header 323 * already exists. 324 * 325 * @param state the {@link HttpState state} information associated with this method 326 * @param conn the {@link HttpConnection connection} used to execute 327 * this HTTP method 328 * 329 * @throws IOException if an I/O (transport) error occurs 330 * @throws HttpException if a protocol exception occurs. 331 * @throws HttpRecoverableException if a recoverable transport error occurs. 332 * Usually this kind of exceptions can be recovered from by 333 * retrying the HTTP method 334 */ 335 protected void addContentLengthRequestHeader(HttpState state, 336 HttpConnection conn) 337 throws IOException, HttpException { 338 LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader(" 339 + "HttpState, HttpConnection)"); 340 341 if ((getRequestHeader("content-length") == null) 342 && (getRequestHeader("Transfer-Encoding") == null)) { 343 int len = getRequestContentLength(); 344 if (len >= 0) { 345 addRequestHeader("Content-Length", String.valueOf(len)); 346 } else if ((len == CONTENT_LENGTH_CHUNKED) && (isHttp11())) { 347 addRequestHeader("Transfer-Encoding", "chunked"); 348 } 349 } 350 } 351 352 /*** 353 * Sets the request body to be the specified inputstream. 354 * 355 * @param body Request body content as {@link java.io.InputStream} 356 */ 357 public void setRequestBody(InputStream body) { 358 LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)"); 359 clearRequestBody(); 360 this.requestStream = body; 361 } 362 363 /*** 364 * Returns the request body as a {@link java.io.InputStream}. 365 * Calling this method will cause the content to be buffered. 366 * 367 * @return The request body as a {@link java.io.InputStream} if it has been set. 368 */ 369 public InputStream getRequestBody() { 370 LOG.trace("enter EntityEnclosingMethod.getRequestBody()"); 371 byte [] content = generateRequestBody(); 372 if (content != null) { 373 return new ByteArrayInputStream(content); 374 } else { 375 return new ByteArrayInputStream(new byte[] {}); 376 } 377 } 378 379 /*** 380 * Sets the request body to be the specified string. 381 * The string will be submitted, using the encoding 382 * specified in the Content-Type request header.<br> 383 * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br> 384 * Would use the UTF-8 encoding. 385 * If no charset is specified, the 386 * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default} 387 * content encoding is used (ISO-8859-1). 388 * 389 * @param body Request body content as a string 390 */ 391 public void setRequestBody(String body) { 392 LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)"); 393 clearRequestBody(); 394 this.requestString = body; 395 } 396 397 /*** 398 * Returns the request body as a {@link java.lang.String}. 399 * Calling this method will cause the content to be buffered. 400 * 401 * @return the request body as a {@link java.lang.String} 402 * 403 * @throws IOException when i/o error occurs while reading the request 404 */ 405 public String getRequestBodyAsString() throws IOException { 406 LOG.trace("enter EntityEnclosingMethod.getRequestBodyAsString()"); 407 byte [] content = generateRequestBody(); 408 if (content != null) { 409 return HttpConstants.getContentString(content, getRequestCharSet()); 410 } else { 411 return null; 412 } 413 } 414 415 416 /*** 417 * Writes the request body to the given {@link HttpConnection connection}. 418 * 419 * @param state the {@link HttpState state} information associated with this method 420 * @param conn the {@link HttpConnection connection} used to execute 421 * this HTTP method 422 * 423 * @return <tt>true</tt> 424 * 425 * @throws IOException if an I/O (transport) error occurs 426 * @throws HttpException if a protocol exception occurs. 427 * @throws HttpRecoverableException if a recoverable transport error occurs. 428 * Usually this kind of exceptions can be recovered from by 429 * retrying the HTTP method 430 */ 431 protected boolean writeRequestBody(HttpState state, HttpConnection conn) 432 throws IOException, HttpException { 433 LOG.trace( 434 "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)"); 435 436 if (!hasRequestContent()) { 437 LOG.debug("Request body has not been specified"); 438 return true; 439 } 440 441 int contentLength = getRequestContentLength(); 442 443 if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) { 444 throw new HttpException( 445 "Chunked transfer encoding not allowed for HTTP/1.0"); 446 } 447 448 InputStream instream = null; 449 if (this.requestStream != null) { 450 LOG.debug("Using unbuffered request body"); 451 instream = this.requestStream; 452 } else { 453 if (this.contentCache == null) { 454 this.contentCache = generateRequestBody(); 455 } 456 if (this.contentCache != null) { 457 LOG.debug("Using buffered request body"); 458 instream = new ByteArrayInputStream(this.contentCache); 459 } 460 } 461 462 if (instream == null) { 463 LOG.debug("Request body is empty"); 464 return true; 465 } 466 467 if ((this.repeatCount > 0) && (this.contentCache == null)) { 468 throw new HttpException( 469 "Unbuffered entity enclosing request can not be repeated."); 470 } 471 472 this.repeatCount++; 473 474 OutputStream outstream = conn.getRequestOutputStream(); 475 476 if (contentLength == CONTENT_LENGTH_CHUNKED) { 477 outstream = new ChunkedOutputStream(outstream); 478 } 479 if (contentLength >= 0) { 480 // don't need a watcher here - we're reading from something local, 481 // not server-side. 482 instream = new ContentLengthInputStream(instream, contentLength); 483 } 484 485 byte[] tmp = new byte[4096]; 486 int total = 0; 487 int i = 0; 488 while ((i = instream.read(tmp)) >= 0) { 489 outstream.write(tmp, 0, i); 490 total += i; 491 } 492 // This is hardly the most elegant solution to closing chunked stream 493 if (outstream instanceof ChunkedOutputStream) { 494 ((ChunkedOutputStream) outstream).writeClosingChunk(); 495 } 496 if ((contentLength > 0) && (total < contentLength)) { 497 throw new IOException("Unexpected end of input stream after " 498 + total + " bytes (expected " + contentLength + " bytes)"); 499 } 500 LOG.debug("Request body sent"); 501 return true; 502 } 503 504 /*** 505 * Recycles the HTTP method so that it can be used again. 506 * Note that all of the instance variables will be reset 507 * once this method has been called. This method will also 508 * release the connection being used by this HTTP method. 509 * 510 * @see #releaseConnection() 511 */ 512 public void recycle() { 513 LOG.trace("enter EntityEnclosingMethod.recycle()"); 514 clearRequestBody(); 515 this.requestContentLength = CONTENT_LENGTH_AUTO; 516 this.repeatCount = 0; 517 super.recycle(); 518 } 519 520 /*** 521 * Buffers request body input stream. 522 */ 523 private void bufferContent() { 524 LOG.trace("enter EntityEnclosingMethod.bufferContent()"); 525 526 if (this.buffer != null) { 527 // Already been buffered 528 return; 529 } 530 if (this.requestStream != null) { 531 try { 532 ByteArrayOutputStream tmp = new ByteArrayOutputStream(); 533 byte[] data = new byte[4096]; 534 int l = 0; 535 while ((l = this.requestStream.read(data)) >= 0) { 536 tmp.write(data, 0, l); 537 } 538 this.buffer = tmp.toByteArray(); 539 this.requestStream = null; 540 } catch (IOException e) { 541 LOG.error(e.getMessage(), e); 542 this.buffer = null; 543 this.requestStream = null; 544 } 545 } 546 } 547 }

This page was automatically generated by Maven