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