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