View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.17 2003/05/15 18:06:02 olegk Exp $ 3 * $Revision: 1.17 $ 4 * $Date: 2003/05/15 18:06:02 $ 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.17 $ 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 overwritten 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 overwritten 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 overwritten 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</null>. 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 * Override 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 * Adds a <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 current state of http requests 326 * @param conn the connection to use for I/O 327 * 328 * @throws IOException when errors occur reading or writing to/from the 329 * connection 330 * @throws HttpException when a recoverable error occurs 331 */ 332 protected void addContentLengthRequestHeader(HttpState state, 333 HttpConnection conn) 334 throws IOException, HttpException { 335 LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader(" 336 + "HttpState, HttpConnection)"); 337 338 if ((getRequestHeader("content-length") == null) && 339 (getRequestHeader("Transfer-Encoding") == null)) { 340 int len = getRequestContentLength(); 341 if (len >= 0) { 342 addRequestHeader("Content-Length", String.valueOf(len)); 343 } else if ((len == CONTENT_LENGTH_CHUNKED) && (isHttp11())) { 344 addRequestHeader("Transfer-Encoding", "chunked"); 345 } 346 } 347 } 348 349 /*** 350 * Sets the request body to be the specified inputstream. 351 * 352 * @param body Request body content as {@link java.io.InputStream} 353 */ 354 public void setRequestBody(InputStream body) { 355 LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)"); 356 clearRequestBody(); 357 this.requestStream = body; 358 } 359 360 /*** 361 * Gets the request body as a stream. 362 * Calling this method will cause the content to be buffered. 363 * 364 * @return The request body {@link java.io.InputStream} if it has been set. 365 */ 366 public InputStream getRequestBody() { 367 LOG.trace("enter EntityEnclosingMethod.getRequestBody()"); 368 byte [] content = generateRequestBody(); 369 if (content != null) { 370 return new ByteArrayInputStream(content); 371 } else { 372 return new ByteArrayInputStream(new byte[] {}); 373 } 374 } 375 376 /*** 377 * Sets the request body to be the specified string. 378 * 379 * @param body Request body content as a string 380 */ 381 public void setRequestBody(String body) { 382 LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)"); 383 clearRequestBody(); 384 this.requestString = body; 385 } 386 387 /*** 388 * Gets the request body as a String. 389 * Calling this method will cause the content to be buffered. 390 * 391 * @return the request body as a string 392 * 393 * @throws IOException when i/o errors occur reading the request 394 */ 395 public String getRequestBodyAsString() throws IOException { 396 LOG.trace("enter EntityEnclosingMethod.getRequestBodyAsString()"); 397 byte [] content = generateRequestBody(); 398 if (content != null) { 399 return HttpConstants.getContentString(content, getRequestCharSet()); 400 } else { 401 return null; 402 } 403 } 404 405 406 /*** 407 * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} 408 * to write request parameters as the request body. The input stream will 409 * be truncated after the specified content length. 410 * 411 * @param state the client state 412 * @param conn the connection to write to 413 * 414 * @return <tt>true</tt> 415 * @throws IOException when i/o errors occur reading the response 416 * @throws HttpException when a protocol error occurs or state is invalid 417 */ 418 protected boolean writeRequestBody(HttpState state, HttpConnection conn) 419 throws IOException, HttpException { 420 LOG.trace( 421 "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)"); 422 423 if (!hasRequestContent()) { 424 LOG.debug("Request body has not been specified"); 425 return true; 426 } 427 428 int contentLength = getRequestContentLength(); 429 430 if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) { 431 throw new HttpException( 432 "Chunked transfer encoding not allowed for HTTP/1.0"); 433 } 434 435 InputStream instream = null; 436 if (this.requestStream != null) { 437 LOG.debug("Using unbuffered request body"); 438 instream = this.requestStream; 439 } else { 440 if (this.contentCache == null) { 441 this.contentCache = generateRequestBody(); 442 } 443 if (this.contentCache != null) { 444 LOG.debug("Using buffered request body"); 445 instream = new ByteArrayInputStream(this.contentCache); 446 } 447 } 448 449 if (instream == null) { 450 LOG.debug("Request body is empty"); 451 return true; 452 } 453 454 if ((this.repeatCount > 0) && (this.contentCache == null)) { 455 throw new HttpException( 456 "Unbuffered entity enclosing request can not be repeated."); 457 } 458 459 this.repeatCount++; 460 461 OutputStream outstream = conn.getRequestOutputStream(); 462 463 if (contentLength == CONTENT_LENGTH_CHUNKED) { 464 outstream = new ChunkedOutputStream(outstream); 465 } 466 if (contentLength >= 0) { 467 // don't need a watcher here - we're reading from something local, 468 // not server-side. 469 instream = new ContentLengthInputStream(instream, contentLength); 470 } 471 472 byte[] tmp = new byte[4096]; 473 int total = 0; 474 int i = 0; 475 while ((i = instream.read(tmp)) >= 0) { 476 outstream.write(tmp, 0, i); 477 total += i; 478 } 479 // This is hardly the most elegant solution to closing chunked stream 480 if (outstream instanceof ChunkedOutputStream) { 481 ((ChunkedOutputStream) outstream).writeClosingChunk(); 482 } 483 if ((contentLength > 0) && (total < contentLength)) { 484 throw new IOException("Unexpected end of input stream after " 485 + total + " bytes (expected " + contentLength + " bytes)"); 486 } 487 LOG.debug("Request body sent"); 488 return true; 489 } 490 491 /*** 492 * Override method of {@link org.apache.commons.httpclient.HttpMethodBase} 493 * to clear my request body. 494 */ 495 public void recycle() { 496 LOG.trace("enter EntityEnclosingMethod.recycle()"); 497 clearRequestBody(); 498 this.requestContentLength = CONTENT_LENGTH_AUTO; 499 this.repeatCount = 0; 500 super.recycle(); 501 } 502 503 /*** 504 * Buffers request body input stream. 505 */ 506 private void bufferContent() { 507 LOG.trace("enter EntityEnclosingMethod.bufferContent()"); 508 509 if (this.buffer != null) { 510 // Already been buffered 511 return; 512 } 513 if (this.requestStream != null) { 514 try { 515 ByteArrayOutputStream tmp = new ByteArrayOutputStream(); 516 byte[] data = new byte[4096]; 517 int l = 0; 518 while ((l = this.requestStream.read(data)) >= 0) { 519 tmp.write(data, 0, l); 520 } 521 this.buffer = tmp.toByteArray(); 522 this.requestStream = null; 523 } catch (IOException e) { 524 LOG.error(e.getMessage(), e); 525 this.buffer = null; 526 this.requestStream = null; 527 } 528 } 529 } 530 }

This page was automatically generated by Maven