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: 161963 $
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 this.requestEntity = new StringRequestEntity(
193 requestString);
194 }
195 }
196
197 return this.requestEntity;
198 }
199
200 /***
201 * Entity enclosing requests cannot be redirected without user intervention
202 * according to RFC 2616.
203 *
204 * @return <code>false</code>.
205 *
206 * @since 2.0
207 */
208 public boolean getFollowRedirects() {
209 return false;
210 }
211
212
213 /***
214 * Entity enclosing requests cannot be redirected without user intervention
215 * according to RFC 2616.
216 *
217 * @param followRedirects must always be <code>false</code>
218 */
219 public void setFollowRedirects(boolean followRedirects) {
220 if (followRedirects == true) {
221 throw new IllegalArgumentException("Entity enclosing requests cannot be redirected without user intervention");
222 }
223 super.setFollowRedirects(false);
224 }
225
226 /***
227 * Sets length information about the request body.
228 *
229 * <p>
230 * Note: If you specify a content length the request is unbuffered. This
231 * prevents redirection and automatic retry if a request fails the first
232 * time. This means that the HttpClient can not perform authorization
233 * automatically but will throw an Exception. You will have to set the
234 * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
235 * </p>
236 *
237 * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
238 * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
239 * is specified the content will not be buffered internally and the
240 * Content-Length header of the request will be used. In this case
241 * the user is responsible to supply the correct content length.
242 * If CONTENT_LENGTH_AUTO is specified the request will be buffered
243 * before it is sent over the network.
244 *
245 * @deprecated Use {@link #setContentChunked(boolean)} or
246 * {@link #setRequestEntity(RequestEntity)}
247 */
248 public void setRequestContentLength(int length) {
249 LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
250 this.requestContentLength = length;
251 }
252
253 /***
254 * Returns the request's charset. The charset is parsed from the request entity's
255 * content type, unless the content type header has been set manually.
256 *
257 * @see RequestEntity#getContentType()
258 *
259 * @since 3.0
260 */
261 public String getRequestCharSet() {
262 if (getRequestHeader("Content-Type") == null) {
263
264
265
266 if (this.requestEntity != null) {
267 return getContentCharSet(
268 new Header("Content-Type", requestEntity.getContentType()));
269 } else {
270 return super.getRequestCharSet();
271 }
272 } else {
273 return super.getRequestCharSet();
274 }
275 }
276
277 /***
278 * Sets length information about the request body.
279 *
280 * <p>
281 * Note: If you specify a content length the request is unbuffered. This
282 * prevents redirection and automatic retry if a request fails the first
283 * time. This means that the HttpClient can not perform authorization
284 * automatically but will throw an Exception. You will have to set the
285 * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
286 * </p>
287 *
288 * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
289 * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
290 * is specified the content will not be buffered internally and the
291 * Content-Length header of the request will be used. In this case
292 * the user is responsible to supply the correct content length.
293 * If CONTENT_LENGTH_AUTO is specified the request will be buffered
294 * before it is sent over the network.
295 *
296 * @deprecated Use {@link #setContentChunked(boolean)} or
297 * {@link #setRequestEntity(RequestEntity)}
298 */
299 public void setRequestContentLength(long length) {
300 LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
301 this.requestContentLength = length;
302 }
303
304 /***
305 * Sets whether or not the content should be chunked.
306 *
307 * @param chunked <code>true</code> if the content should be chunked
308 *
309 * @since 3.0
310 */
311 public void setContentChunked(boolean chunked) {
312 this.chunked = chunked;
313 }
314
315 /***
316 * Returns the length of the request body.
317 *
318 * @return number of bytes in the request body
319 */
320 protected long getRequestContentLength() {
321 LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
322
323 if (!hasRequestContent()) {
324 return 0;
325 }
326 if (this.chunked) {
327 return -1;
328 }
329 if (this.requestEntity == null) {
330 this.requestEntity = generateRequestEntity();
331 }
332 return (this.requestEntity == null) ? 0 : this.requestEntity.getContentLength();
333 }
334
335 /***
336 * Populates the request headers map to with additional
337 * {@link org.apache.commons.httpclient.Header headers} to be submitted to
338 * the given {@link HttpConnection}.
339 *
340 * <p>
341 * This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
342 * headers.
343 * </p>
344 *
345 * <p>
346 * Subclasses may want to override this method to to add additional
347 * headers, and may choose to invoke this implementation (via
348 * <tt>super</tt>) to add the "standard" headers.
349 * </p>
350 *
351 * @param state the {@link HttpState state} information associated with this method
352 * @param conn the {@link HttpConnection connection} used to execute
353 * this HTTP method
354 *
355 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
356 * can be recovered from.
357 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
358 * cannot be recovered from.
359 *
360 * @see #writeRequestHeaders
361 *
362 * @since 3.0
363 */
364 protected void addRequestHeaders(HttpState state, HttpConnection conn)
365 throws IOException, HttpException {
366 LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
367 + "HttpConnection)");
368
369 super.addRequestHeaders(state, conn);
370 addContentLengthRequestHeader(state, conn);
371
372
373
374 if (getRequestHeader("Content-Type") == null) {
375 RequestEntity requestEntity = getRequestEntity();
376 if (requestEntity != null && requestEntity.getContentType() != null) {
377 setRequestHeader("Content-Type", requestEntity.getContentType());
378 }
379 }
380 }
381
382 /***
383 * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
384 * request header, as long as no <tt>Content-Length</tt> request header
385 * already exists.
386 *
387 * @param state current state of http requests
388 * @param conn the connection to use for I/O
389 *
390 * @throws IOException when errors occur reading or writing to/from the
391 * connection
392 * @throws HttpException when a recoverable error occurs
393 */
394 protected void addContentLengthRequestHeader(HttpState state,
395 HttpConnection conn)
396 throws IOException, HttpException {
397 LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
398 + "HttpState, HttpConnection)");
399
400 if ((getRequestHeader("content-length") == null)
401 && (getRequestHeader("Transfer-Encoding") == null)) {
402 long len = getRequestContentLength();
403 if (len < 0) {
404 if (getEffectiveVersion().greaterEquals(HttpVersion.HTTP_1_1)) {
405 addRequestHeader("Transfer-Encoding", "chunked");
406 } else {
407 throw new ProtocolException(getEffectiveVersion() +
408 " does not support chunk encoding");
409 }
410 } else {
411 addRequestHeader("Content-Length", String.valueOf(len));
412 }
413 }
414 }
415
416 /***
417 * Sets the request body to be the specified inputstream.
418 *
419 * @param body Request body content as {@link java.io.InputStream}
420 *
421 * @deprecated use {@link #setRequestEntity(RequestEntity)}
422 */
423 public void setRequestBody(InputStream body) {
424 LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
425 clearRequestBody();
426 this.requestStream = body;
427 }
428
429 /***
430 * Sets the request body to be the specified string.
431 * The string will be submitted, using the encoding
432 * specified in the Content-Type request header.<br>
433 * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
434 * Would use the UTF-8 encoding.
435 * If no charset is specified, the
436 * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
437 * content encoding is used (ISO-8859-1).
438 *
439 * @param body Request body content as a string
440 *
441 * @deprecated use {@link #setRequestEntity(RequestEntity)}
442 */
443 public void setRequestBody(String body) {
444 LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
445 clearRequestBody();
446 this.requestString = body;
447 }
448
449 /***
450 * Writes the request body to the given {@link HttpConnection connection}.
451 *
452 * @param state the {@link HttpState state} information associated with this method
453 * @param conn the {@link HttpConnection connection} used to execute
454 * this HTTP method
455 *
456 * @return <tt>true</tt>
457 *
458 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
459 * can be recovered from.
460 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
461 * cannot be recovered from.
462 */
463 protected boolean writeRequestBody(HttpState state, HttpConnection conn)
464 throws IOException, HttpException {
465 LOG.trace(
466 "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
467
468 if (!hasRequestContent()) {
469 LOG.debug("Request body has not been specified");
470 return true;
471 }
472 if (this.requestEntity == null) {
473 this.requestEntity = generateRequestEntity();
474 }
475 if (requestEntity == null) {
476 LOG.debug("Request body is empty");
477 return true;
478 }
479
480 long contentLength = getRequestContentLength();
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 < 0) {
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 = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
523 this.repeatCount = 0;
524 this.chunked = false;
525 super.recycle();
526 }
527
528 /***
529 * @return Returns the requestEntity.
530 *
531 * @since 3.0
532 */
533 public RequestEntity getRequestEntity() {
534 return generateRequestEntity();
535 }
536
537 /***
538 * @param requestEntity The requestEntity to set.
539 *
540 * @since 3.0
541 */
542 public void setRequestEntity(RequestEntity requestEntity) {
543 clearRequestBody();
544 this.requestEntity = requestEntity;
545 }
546
547 }