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