1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
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
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
177
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
268
269
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
377
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
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 }