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