View Javadoc

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