View Javadoc

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