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 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
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
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
171
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
252
253
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
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
363
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
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 }