View Javadoc

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