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
32 package org.apache.commons.httpclient.methods;
33
34 import java.io.ByteArrayInputStream;
35 import java.io.ByteArrayOutputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39
40 import org.apache.commons.httpclient.ChunkedOutputStream;
41 import org.apache.commons.httpclient.ContentLengthInputStream;
42 import org.apache.commons.httpclient.HttpConnection;
43 import org.apache.commons.httpclient.HttpConstants;
44 import org.apache.commons.httpclient.HttpException;
45 import org.apache.commons.httpclient.HttpState;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48
49 /***
50 * This abstract class serves as a foundation for all HTTP methods
51 * that can enclose an entity within requests
52 *
53 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
54 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
55 *
56 * @since 2.0beta1
57 * @version $Revision: 1.18.2.5 $
58 */
59 public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
60
61
62
63 /***
64 * The content length will be calculated automatically. This implies
65 * buffering of the content.
66 */
67 public static final int CONTENT_LENGTH_AUTO = -2;
68
69 /***
70 * The request will use chunked transfer encoding. Content length is not
71 * calculated and the content is not buffered.<br>
72 */
73 public static final int CONTENT_LENGTH_CHUNKED = -1;
74
75 /*** LOG object for this class. */
76 private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
77
78 /*** The buffered request body, if any. */
79 private byte[] buffer = null;
80
81 /*** The unbuffered request body, if any. */
82 private InputStream requestStream = null;
83
84 /*** The request body as string, if any. */
85 private String requestString = null;
86
87 /*** for optimization purpose, the generated request body may be
88 * cached when the method is being executed.
89 */
90 private byte[] contentCache = null;
91
92 /*** Counts how often the request was sent to the server. */
93 private int repeatCount = 0;
94
95 /*** The content length of the <code>requestBodyStream</code> or one of
96 * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
97 */
98 private int requestContentLength = CONTENT_LENGTH_AUTO;
99
100
101
102 /***
103 * No-arg constructor.
104 *
105 * @since 2.0
106 */
107 public EntityEnclosingMethod() {
108 super();
109 setFollowRedirects(false);
110 }
111
112 /***
113 * Constructor specifying a URI.
114 *
115 * @param uri either an absolute or relative URI
116 *
117 * @since 2.0
118 */
119 public EntityEnclosingMethod(String uri) {
120 super(uri);
121 setFollowRedirects(false);
122 }
123
124 /***
125 * Constructor specifying a URI and a tempDir.
126 *
127 * @param uri either an absolute or relative URI
128 * @param tempDir directory to store temp files in
129 *
130 * @deprecated the client is responsible for disk I/O
131 * @since 2.0
132 */
133 public EntityEnclosingMethod(String uri, String tempDir) {
134 super(uri, tempDir);
135 setFollowRedirects(false);
136 }
137
138 /***
139 * Constructor specifying a URI, tempDir and tempFile.
140 *
141 * @param uri either an absolute or relative URI
142 * @param tempDir directory to store temp files in
143 * @param tempFile file to store temporary data in
144 *
145 * @deprecated the client is responsible for disk I/O
146 * @since 2.0
147 */
148 public EntityEnclosingMethod(String uri, String tempDir, String tempFile) {
149 super(uri, tempDir, tempFile);
150 setFollowRedirects(false);
151 }
152
153 /***
154 * Returns <tt>true</tt> if there is a request body to be sent.
155 *
156 * <P>This method must be overridden by sub-classes that implement
157 * alternative request content input methods
158 * </p>
159 *
160 * @return boolean
161 *
162 * @since 2.0beta1
163 */
164 protected boolean hasRequestContent() {
165 LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
166 return (this.buffer != null)
167 || (this.requestStream != null)
168 || (this.requestString != null);
169 }
170
171 /***
172 * Clears request body.
173 *
174 * <p>This method must be overridden by sub-classes that implement
175 * alternative request content input methods.</p>
176 *
177 * @since 2.0beta1
178 */
179 protected void clearRequestBody() {
180 LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
181 this.requestStream = null;
182 this.requestString = null;
183 this.buffer = null;
184 this.contentCache = null;
185 }
186
187 /***
188 * Generates request body.
189 *
190 * <p>This method must be overridden by sub-classes that implement
191 * alternative request content input methods.</p>
192 *
193 * @return request body as an array of bytes. If the request content
194 * has not been set, returns <tt>null</tt>.
195 *
196 * @since 2.0beta1
197 */
198 protected byte[] generateRequestBody() {
199 LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
200 if (this.requestStream != null) {
201 bufferContent();
202 }
203
204 if (this.buffer != null) {
205 return this.buffer;
206 } else if (this.requestString != null) {
207 return HttpConstants.getContentBytes(this.requestString, getRequestCharSet());
208 } else {
209 return null;
210 }
211 }
212
213 /***
214 * Entity enclosing requests cannot be redirected without user intervention
215 * according to RFC 2616.
216 *
217 * @return <code>false</code>.
218 *
219 * @since 2.0
220 */
221 public boolean getFollowRedirects() {
222 return false;
223 }
224
225
226 /***
227 * Entity enclosing requests cannot be redirected without user intervention
228 * according to RFC 2616.
229 *
230 * @param followRedirects must always be <code>false</code>
231 */
232 public void setFollowRedirects(boolean followRedirects) {
233 if (followRedirects == true) {
234
235
236
237
238 }
239 super.setFollowRedirects(false);
240 }
241
242 /***
243 * Sets length information about the request body.
244 *
245 * <p>
246 * Note: If you specify a content length the request is unbuffered. This
247 * prevents redirection and automatic retry if a request fails the first
248 * time. This means that the HttpClient can not perform authorization
249 * automatically but will throw an Exception. You will have to set the
250 * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
251 * </p>
252 *
253 * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
254 * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
255 * is specified the content will not be buffered internally and the
256 * Content-Length header of the request will be used. In this case
257 * the user is responsible to supply the correct content length.
258 * If CONTENT_LENGTH_AUTO is specified the request will be buffered
259 * before it is sent over the network.
260 *
261 */
262 public void setRequestContentLength(int length) {
263 LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
264 this.requestContentLength = length;
265 }
266
267 /***
268 * Overrides method of {@link org.apache.commons.httpclient.HttpMethodBase}
269 * to return the length of the request body.
270 *
271 * @return number of bytes in the request body
272 */
273 protected int getRequestContentLength() {
274 LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
275
276 if (!hasRequestContent()) {
277 return 0;
278 }
279 if (this.requestContentLength != CONTENT_LENGTH_AUTO) {
280 return this.requestContentLength;
281 }
282 if (this.contentCache == null) {
283 this.contentCache = generateRequestBody();
284 }
285 return (this.contentCache == null) ? 0 : this.contentCache.length;
286 }
287
288 /***
289 * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
290 * request header, as long as no <tt>Content-Length</tt> request header
291 * already exists.
292 *
293 * @param state the {@link HttpState state} information associated with this method
294 * @param conn the {@link HttpConnection connection} used to execute
295 * this HTTP method
296 *
297 * @throws IOException if an I/O (transport) error occurs
298 * @throws HttpException if a protocol exception occurs.
299 * @throws HttpRecoverableException if a recoverable transport error occurs.
300 * Usually this kind of exceptions can be recovered from by
301 * retrying the HTTP method
302 */
303 protected void addContentLengthRequestHeader(HttpState state,
304 HttpConnection conn)
305 throws IOException, HttpException {
306 LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader("
307 + "HttpState, HttpConnection)");
308
309 if ((getRequestHeader("content-length") == null)
310 && (getRequestHeader("Transfer-Encoding") == null)) {
311 int len = getRequestContentLength();
312 if (len >= 0) {
313 addRequestHeader("Content-Length", String.valueOf(len));
314 } else if ((len == CONTENT_LENGTH_CHUNKED) && (isHttp11())) {
315 addRequestHeader("Transfer-Encoding", "chunked");
316 }
317 }
318 }
319
320 /***
321 * Sets the request body to be the specified inputstream.
322 *
323 * @param body Request body content as {@link java.io.InputStream}
324 */
325 public void setRequestBody(InputStream body) {
326 LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
327 clearRequestBody();
328 this.requestStream = body;
329 }
330
331 /***
332 * Returns the request body as a {@link java.io.InputStream}.
333 * Calling this method will cause the content to be buffered.
334 *
335 * @return The request body as a {@link java.io.InputStream} if it has been set.
336 */
337 public InputStream getRequestBody() {
338 LOG.trace("enter EntityEnclosingMethod.getRequestBody()");
339 byte [] content = generateRequestBody();
340 if (content != null) {
341 return new ByteArrayInputStream(content);
342 } else {
343 return new ByteArrayInputStream(new byte[] {});
344 }
345 }
346
347 /***
348 * Sets the request body to be the specified string.
349 * The string will be submitted, using the encoding
350 * specified in the Content-Type request header.<br>
351 * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
352 * Would use the UTF-8 encoding.
353 * If no charset is specified, the
354 * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
355 * content encoding is used (ISO-8859-1).
356 *
357 * @param body Request body content as a string
358 */
359 public void setRequestBody(String body) {
360 LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
361 clearRequestBody();
362 this.requestString = body;
363 }
364
365 /***
366 * Returns the request body as a {@link java.lang.String}.
367 * Calling this method will cause the content to be buffered.
368 *
369 * @return the request body as a {@link java.lang.String}
370 *
371 * @throws IOException when i/o error occurs while reading the request
372 */
373 public String getRequestBodyAsString() throws IOException {
374 LOG.trace("enter EntityEnclosingMethod.getRequestBodyAsString()");
375 byte [] content = generateRequestBody();
376 if (content != null) {
377 return HttpConstants.getContentString(content, getRequestCharSet());
378 } else {
379 return null;
380 }
381 }
382
383
384 /***
385 * Writes the request body to the given {@link HttpConnection connection}.
386 *
387 * @param state the {@link HttpState state} information associated with this method
388 * @param conn the {@link HttpConnection connection} used to execute
389 * this HTTP method
390 *
391 * @return <tt>true</tt>
392 *
393 * @throws IOException if an I/O (transport) error occurs
394 * @throws HttpException if a protocol exception occurs.
395 * @throws HttpRecoverableException if a recoverable transport error occurs.
396 * Usually this kind of exceptions can be recovered from by
397 * retrying the HTTP method
398 */
399 protected boolean writeRequestBody(HttpState state, HttpConnection conn)
400 throws IOException, HttpException {
401 LOG.trace(
402 "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
403
404 if (!hasRequestContent()) {
405 LOG.debug("Request body has not been specified");
406 return true;
407 }
408
409 int contentLength = getRequestContentLength();
410
411 if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) {
412 throw new HttpException(
413 "Chunked transfer encoding not allowed for HTTP/1.0");
414 }
415
416 InputStream instream = null;
417 if (this.requestStream != null) {
418 LOG.debug("Using unbuffered request body");
419 instream = this.requestStream;
420 } else {
421 if (this.contentCache == null) {
422 this.contentCache = generateRequestBody();
423 }
424 if (this.contentCache != null) {
425 LOG.debug("Using buffered request body");
426 instream = new ByteArrayInputStream(this.contentCache);
427 }
428 }
429
430 if (instream == null) {
431 LOG.debug("Request body is empty");
432 return true;
433 }
434
435 if ((this.repeatCount > 0) && (this.contentCache == null)) {
436 throw new HttpException(
437 "Unbuffered entity enclosing request can not be repeated.");
438 }
439
440 this.repeatCount++;
441
442 OutputStream outstream = conn.getRequestOutputStream();
443
444 if (contentLength == CONTENT_LENGTH_CHUNKED) {
445 outstream = new ChunkedOutputStream(outstream);
446 }
447 if (contentLength >= 0) {
448
449
450 instream = new ContentLengthInputStream(instream, contentLength);
451 }
452
453 byte[] tmp = new byte[4096];
454 int total = 0;
455 int i = 0;
456 while ((i = instream.read(tmp)) >= 0) {
457 outstream.write(tmp, 0, i);
458 total += i;
459 }
460
461 if (outstream instanceof ChunkedOutputStream) {
462 ((ChunkedOutputStream) outstream).writeClosingChunk();
463 }
464 if ((contentLength > 0) && (total < contentLength)) {
465 throw new IOException("Unexpected end of input stream after "
466 + total + " bytes (expected " + contentLength + " bytes)");
467 }
468 LOG.debug("Request body sent");
469 return true;
470 }
471
472 /***
473 * Recycles the HTTP method so that it can be used again.
474 * Note that all of the instance variables will be reset
475 * once this method has been called. This method will also
476 * release the connection being used by this HTTP method.
477 *
478 * @see #releaseConnection()
479 *
480 * @deprecated no longer supported and will be removed in the future
481 * version of HttpClient
482 */
483 public void recycle() {
484 LOG.trace("enter EntityEnclosingMethod.recycle()");
485 clearRequestBody();
486 this.requestContentLength = CONTENT_LENGTH_AUTO;
487 this.repeatCount = 0;
488 super.recycle();
489 }
490
491 /***
492 * Buffers request body input stream.
493 */
494 private void bufferContent() {
495 LOG.trace("enter EntityEnclosingMethod.bufferContent()");
496
497 if (this.buffer != null) {
498
499 return;
500 }
501 if (this.requestStream != null) {
502 try {
503 ByteArrayOutputStream tmp = new ByteArrayOutputStream();
504 byte[] data = new byte[4096];
505 int l = 0;
506 while ((l = this.requestStream.read(data)) >= 0) {
507 tmp.write(data, 0, l);
508 }
509 this.buffer = tmp.toByteArray();
510 this.requestStream = null;
511 } catch (IOException e) {
512 LOG.error(e.getMessage(), e);
513 this.buffer = null;
514 this.requestStream = null;
515 }
516 }
517 }
518 }