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;
31
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36
37 import org.apache.commons.httpclient.auth.AuthState;
38 import org.apache.commons.httpclient.cookie.CookiePolicy;
39 import org.apache.commons.httpclient.cookie.CookieSpec;
40 import org.apache.commons.httpclient.cookie.MalformedCookieException;
41 import org.apache.commons.httpclient.params.HttpMethodParams;
42 import org.apache.commons.httpclient.protocol.Protocol;
43 import org.apache.commons.httpclient.util.EncodingUtil;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46
47 /***
48 * An abstract base implementation of HttpMethod.
49 * <p>
50 * At minimum, subclasses will need to override:
51 * <ul>
52 * <li>{@link #getName} to return the approriate name for this method
53 * </li>
54 * </ul>
55 * </p>
56 *
57 * <p>
58 * When a method requires additional request headers, subclasses will typically
59 * want to override:
60 * <ul>
61 * <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
62 * to write those headers
63 * </li>
64 * </ul>
65 * </p>
66 *
67 * <p>
68 * When a method expects specific response headers, subclasses may want to
69 * override:
70 * <ul>
71 * <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
72 * to handle those headers
73 * </li>
74 * </ul>
75 * </p>
76 *
77 *
78 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
79 * @author Rodney Waldhoff
80 * @author Sean C. Sullivan
81 * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
82 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
83 * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
84 * @author Ortwin Glueck
85 * @author Eric Johnson
86 * @author Michael Becke
87 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
88 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
89 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
90 * @author Christian Kohlschuetter
91 *
92 * @version $Revision: 1.207 $ $Date: 2004/05/13 04:03:24 $
93 */
94 public abstract class HttpMethodBase implements HttpMethod {
95
96
97
98 /*** Log object for this class. */
99 private static final Log LOG = LogFactory.getLog(HttpMethodBase.class);
100
101
102
103 /*** Request headers, if any. */
104 private HeaderGroup requestHeaders = new HeaderGroup();
105
106 /*** The Status-Line from the response. */
107 private StatusLine statusLine = null;
108
109 /*** Response headers, if any. */
110 private HeaderGroup responseHeaders = new HeaderGroup();
111
112 /*** Response trailer headers, if any. */
113 private HeaderGroup responseTrailerHeaders = new HeaderGroup();
114
115 /*** Path of the HTTP method. */
116 private String path = null;
117
118 /*** Query string of the HTTP method, if any. */
119 private String queryString = null;
120
121 /*** The response body of the HTTP method, assuming it has not be
122 * intercepted by a sub-class. */
123 private InputStream responseStream = null;
124
125 /*** The connection that the response stream was read from. */
126 private HttpConnection responseConnection = null;
127
128 /*** Buffer for the response */
129 private byte[] responseBody = null;
130
131 /*** True if the HTTP method should automatically follow HTTP redirects.*/
132 private boolean followRedirects = false;
133
134 /*** True if the HTTP method should automatically handle
135 * HTTP authentication challenges. */
136 private boolean doAuthentication = true;
137
138 /*** HTTP protocol parameters. */
139 private HttpMethodParams params = new HttpMethodParams();
140
141 /*** Host authentication state */
142 private AuthState hostAuthState = new AuthState();
143
144 /*** Proxy authentication state */
145 private AuthState proxyAuthState = new AuthState();
146
147 /*** True if this method has already been executed. */
148 private boolean used = false;
149
150 /*** Count of how many times did this HTTP method transparently handle
151 * a recoverable exception. */
152 private int recoverableExceptionCount = 0;
153
154 /*** the host configuration for this HTTP method, can be null */
155 private HostConfiguration hostConfiguration;
156
157 /***
158 * Handles method retries
159 */
160 private MethodRetryHandler methodRetryHandler;
161
162 /*** True if the connection must be closed when no longer needed */
163 private boolean connectionCloseForced = false;
164
165 /*** Number of milliseconds to wait for 100-contunue response. */
166 private static final int RESPONSE_WAIT_TIME_MS = 3000;
167
168 /*** HTTP protocol version used for execution of this method. */
169 private HttpVersion effectiveVersion = null;
170
171 /*** Whether the execution of this method has been aborted */
172 private transient boolean aborted = false;
173
174
175
176 /***
177 * No-arg constructor.
178 */
179 public HttpMethodBase() {
180 }
181
182 /***
183 * Constructor specifying a URI.
184 * It is responsibility of the caller to ensure that URI elements
185 * (path & query parameters) are properly encoded (URL safe).
186 *
187 * @param uri either an absolute or relative URI. The URI is expected
188 * to be URL-encoded
189 *
190 * @throws IllegalArgumentException when URI is invalid
191 * @throws IllegalStateException when protocol of the absolute URI is not recognised
192 */
193 public HttpMethodBase(String uri)
194 throws IllegalArgumentException, IllegalStateException {
195
196 try {
197
198
199 if (uri == null || uri.equals("")) {
200 uri = "/";
201 }
202 setURI(new URI(uri, true));
203 } catch (URIException e) {
204 throw new IllegalArgumentException("Invalid uri '"
205 + uri + "': " + e.getMessage()
206 );
207 }
208 }
209
210
211
212 /***
213 * Obtains the name of the HTTP method as used in the HTTP request line,
214 * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
215 *
216 * @return the name of this method
217 */
218 public abstract String getName();
219
220 /***
221 * Returns the URI of the HTTP method
222 *
223 * @return The URI
224 *
225 * @throws URIException If the URI cannot be created.
226 *
227 * @see org.apache.commons.httpclient.HttpMethod#getURI()
228 */
229 public URI getURI() throws URIException {
230
231 if (hostConfiguration == null) {
232
233 URI tmpUri = new URI(null, null, path, null, null);
234 tmpUri.setEscapedQuery(queryString);
235 return tmpUri;
236 } else {
237
238
239 int port = hostConfiguration.getPort();
240 if (port == hostConfiguration.getProtocol().getDefaultPort()) {
241 port = -1;
242 }
243
244 URI tmpUri = new URI(
245 hostConfiguration.getProtocol().getScheme(),
246 null,
247 hostConfiguration.getHost(),
248 port,
249 path,
250 null
251 );
252 tmpUri.setEscapedQuery(queryString);
253 return tmpUri;
254
255 }
256
257 }
258
259 /***
260 * Sets the URI for this method.
261 *
262 * @param uri URI to be set
263 *
264 * @throws URIException if a URI cannot be set
265 *
266 * @since 3.0
267 */
268 public void setURI(URI uri) throws URIException {
269
270 if (uri.isAbsoluteURI()) {
271 if (this.hostConfiguration == null) {
272 this.hostConfiguration = new HostConfiguration();
273 }
274 this.hostConfiguration.setHost(
275 uri.getHost(),
276 uri.getPort(),
277 uri.getScheme()
278 );
279 }
280
281
282 setPath(
283 uri.getPath() == null
284 ? "/"
285 : uri.getEscapedPath()
286 );
287 setQueryString(uri.getEscapedQuery());
288 }
289
290 /***
291 * Sets whether or not the HTTP method should automatically follow HTTP redirects
292 * (status code 302, etc.)
293 *
294 * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
295 * <tt>false</tt> otherwise.
296 */
297 public void setFollowRedirects(boolean followRedirects) {
298 this.followRedirects = followRedirects;
299 }
300
301 /***
302 * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects
303 * (status code 302, etc.), <tt>false</tt> otherwise.
304 *
305 * @return <tt>true</tt> if the method will automatically follow HTTP redirects,
306 * <tt>false</tt> otherwise.
307 */
308 public boolean getFollowRedirects() {
309 return this.followRedirects;
310 }
311
312 /*** Sets whether version 1.1 of the HTTP protocol should be used per default.
313 *
314 * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
315 *
316 * @deprecated Use {@link HttpMethodParams#setVersion(HttpVersion)}
317 */
318 public void setHttp11(boolean http11) {
319 if (http11) {
320 this.params.setVersion(HttpVersion.HTTP_1_1);
321 } else {
322 this.params.setVersion(HttpVersion.HTTP_1_0);
323 }
324 }
325
326 /***
327 * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP
328 * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
329 *
330 * @return <tt>true</tt> if authentication challenges will be processed
331 * automatically, <tt>false</tt> otherwise.
332 *
333 * @since 2.0
334 */
335 public boolean getDoAuthentication() {
336 return doAuthentication;
337 }
338
339 /***
340 * Sets whether or not the HTTP method should automatically handle HTTP
341 * authentication challenges (status code 401, etc.)
342 *
343 * @param doAuthentication <tt>true</tt> to process authentication challenges
344 * authomatically, <tt>false</tt> otherwise.
345 *
346 * @since 2.0
347 */
348 public void setDoAuthentication(boolean doAuthentication) {
349 this.doAuthentication = doAuthentication;
350 }
351
352
353
354 /***
355 * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be
356 * used per default, <tt>false</tt> if version 1.0 should be used.
357 *
358 * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
359 *
360 * @deprecated Use {@link HttpMethodParams#getVersion()}
361 */
362 public boolean isHttp11() {
363 return this.params.getVersion().equals(HttpVersion.HTTP_1_1);
364 }
365
366 /***
367 * Sets the path of the HTTP method.
368 * It is responsibility of the caller to ensure that the path is
369 * properly encoded (URL safe).
370 *
371 * @param path the path of the HTTP method. The path is expected
372 * to be URL-encoded
373 */
374 public void setPath(String path) {
375 this.path = path;
376 }
377
378 /***
379 * Adds the specified request header, NOT overwriting any previous value.
380 * Note that header-name matching is case insensitive.
381 *
382 * @param header the header to add to the request
383 */
384 public void addRequestHeader(Header header) {
385 LOG.trace("HttpMethodBase.addRequestHeader(Header)");
386
387 if (header == null) {
388 LOG.debug("null header value ignored");
389 } else {
390 getRequestHeaderGroup().addHeader(header);
391 }
392 }
393
394 /***
395 * Use this method internally to add footers.
396 *
397 * @param footer The footer to add.
398 */
399 public void addResponseFooter(Header footer) {
400 getResponseTrailerHeaderGroup().addHeader(footer);
401 }
402
403 /***
404 * Gets the path of this HTTP method.
405 * Calling this method <em>after</em> the request has been executed will
406 * return the <em>actual</em> path, following any redirects automatically
407 * handled by this HTTP method.
408 *
409 * @return the path to request or "/" if the path is blank.
410 */
411 public String getPath() {
412 return (path == null || path.equals("")) ? "/" : path;
413 }
414
415 /***
416 * Sets the query string of this HTTP method. The caller must ensure that the string
417 * is properly URL encoded. The query string should not start with the question
418 * mark character.
419 *
420 * @param queryString the query string
421 *
422 * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
423 */
424 public void setQueryString(String queryString) {
425 this.queryString = queryString;
426 }
427
428 /***
429 * Sets the query string of this HTTP method. The pairs are encoded as UTF-8 characters.
430 * To use a different charset the parameters can be encoded manually using EncodingUtil
431 * and set as a single String.
432 *
433 * @param params an array of {@link NameValuePair}s to add as query string
434 * parameters. The name/value pairs will be automcatically
435 * URL encoded
436 *
437 * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
438 * @see #setQueryString(String)
439 */
440 public void setQueryString(NameValuePair[] params) {
441 LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
442 queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
443 }
444
445 /***
446 * Gets the query string of this HTTP method.
447 *
448 * @return The query string
449 */
450 public String getQueryString() {
451 return queryString;
452 }
453
454 /***
455 * Set the specified request header, overwriting any previous value. Note
456 * that header-name matching is case-insensitive.
457 *
458 * @param headerName the header's name
459 * @param headerValue the header's value
460 */
461 public void setRequestHeader(String headerName, String headerValue) {
462 Header header = new Header(headerName, headerValue);
463 setRequestHeader(header);
464 }
465
466 /***
467 * Sets the specified request header, overwriting any previous value.
468 * Note that header-name matching is case insensitive.
469 *
470 * @param header the header
471 */
472 public void setRequestHeader(Header header) {
473
474 Header[] headers = getRequestHeaderGroup().getHeaders(header.getName());
475
476 for (int i = 0; i < headers.length; i++) {
477 getRequestHeaderGroup().removeHeader(headers[i]);
478 }
479
480 getRequestHeaderGroup().addHeader(header);
481
482 }
483
484 /***
485 * Returns the specified request header. Note that header-name matching is
486 * case insensitive. <tt>null</tt> will be returned if either
487 * <i>headerName</i> is <tt>null</tt> or there is no matching header for
488 * <i>headerName</i>.
489 *
490 * @param headerName The name of the header to be returned.
491 *
492 * @return The specified request header.
493 *
494 * @since 3.0
495 */
496 public Header getRequestHeader(String headerName) {
497 if (headerName == null) {
498 return null;
499 } else {
500 return getRequestHeaderGroup().getCondensedHeader(headerName);
501 }
502 }
503
504 /***
505 * Returns an array of the requests headers that the HTTP method currently has
506 *
507 * @return an array of my request headers.
508 */
509 public Header[] getRequestHeaders() {
510 return getRequestHeaderGroup().getAllHeaders();
511 }
512
513 /***
514 * @see org.apache.commons.httpclient.HttpMethod#getRequestHeaders(java.lang.String)
515 */
516 public Header[] getRequestHeaders(String headerName) {
517 return getRequestHeaderGroup().getHeaders(headerName);
518 }
519
520 /***
521 * Gets the {@link HeaderGroup header group} storing the request headers.
522 *
523 * @return a HeaderGroup
524 *
525 * @since 2.0beta1
526 */
527 protected HeaderGroup getRequestHeaderGroup() {
528 return requestHeaders;
529 }
530
531 /***
532 * Gets the {@link HeaderGroup header group} storing the response trailer headers
533 * as per RFC 2616 section 3.6.1.
534 *
535 * @return a HeaderGroup
536 *
537 * @since 2.0beta1
538 */
539 protected HeaderGroup getResponseTrailerHeaderGroup() {
540 return responseTrailerHeaders;
541 }
542
543 /***
544 * Gets the {@link HeaderGroup header group} storing the response headers.
545 *
546 * @return a HeaderGroup
547 *
548 * @since 2.0beta1
549 */
550 protected HeaderGroup getResponseHeaderGroup() {
551 return responseHeaders;
552 }
553
554 /***
555 * @see org.apache.commons.httpclient.HttpMethod#getResponseHeaders(java.lang.String)
556 *
557 * @since 3.0
558 */
559 public Header[] getResponseHeaders(String headerName) {
560 return getResponseHeaderGroup().getHeaders(headerName);
561 }
562
563 /***
564 * Returns the response status code.
565 *
566 * @return the status code associated with the latest response.
567 */
568 public int getStatusCode() {
569 return statusLine.getStatusCode();
570 }
571
572 /***
573 * Provides access to the response status line.
574 *
575 * @return the status line object from the latest response.
576 * @since 2.0
577 */
578 public StatusLine getStatusLine() {
579 return statusLine;
580 }
581
582 /***
583 * Checks if response data is available.
584 * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
585 */
586 private boolean responseAvailable() {
587 return (responseBody != null) || (responseStream != null);
588 }
589
590 /***
591 * Returns an array of the response headers that the HTTP method currently has
592 * in the order in which they were read.
593 *
594 * @return an array of response headers.
595 */
596 public Header[] getResponseHeaders() {
597 return getResponseHeaderGroup().getAllHeaders();
598 }
599
600 /***
601 * Gets the response header associated with the given name. Header name
602 * matching is case insensitive. <tt>null</tt> will be returned if either
603 * <i>headerName</i> is <tt>null</tt> or there is no matching header for
604 * <i>headerName</i>.
605 *
606 * @param headerName the header name to match
607 *
608 * @return the matching header
609 */
610 public Header getResponseHeader(String headerName) {
611 if (headerName == null) {
612 return null;
613 } else {
614 return getResponseHeaderGroup().getCondensedHeader(headerName);
615 }
616 }
617
618
619 /***
620 * Return the length (in bytes) of the response body, as specified in a
621 * <tt>Content-Length</tt> header.
622 *
623 * <p>
624 * Return <tt>-1</tt> when the content-length is unknown.
625 * </p>
626 *
627 * @return content length, if <tt>Content-Length</tt> header is available.
628 * <tt>0</tt> indicates that the request has no body.
629 * If <tt>Content-Length</tt> header is not present, the method
630 * returns <tt>-1</tt>.
631 */
632 public long getResponseContentLength() {
633 Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length");
634 if (headers.length == 0) {
635 return -1;
636 }
637 if (headers.length > 1) {
638 LOG.warn("Multiple content-length headers detected");
639 }
640 for (int i = headers.length - 1; i >= 0; i--) {
641 Header header = headers[i];
642 try {
643 return Long.parseLong(header.getValue());
644 } catch (NumberFormatException e) {
645 if (LOG.isWarnEnabled()) {
646 LOG.warn("Invalid content-length value: " + e.getMessage());
647 }
648 }
649
650 }
651 return -1;
652 }
653
654
655 /***
656 * Returns the response body of the HTTP method, if any, as an array of bytes.
657 * If response body is not available or cannot be read, returns <tt>null</tt>
658 *
659 * @return The response body.
660 *
661 * @throws IOException If an I/O (transport) problem occurs while obtaining the
662 * response body.
663 */
664 public byte[] getResponseBody() throws IOException {
665 if (this.responseBody == null) {
666 InputStream instream = getResponseBodyAsStream();
667 if (instream != null) {
668 LOG.debug("Buffering response body");
669 ByteArrayOutputStream outstream = new ByteArrayOutputStream();
670 byte[] buffer = new byte[4096];
671 int len;
672 while ((len = instream.read(buffer)) > 0) {
673 outstream.write(buffer, 0, len);
674 }
675 outstream.close();
676 setResponseStream(null);
677 this.responseBody = outstream.toByteArray();
678 }
679 }
680 return this.responseBody;
681 }
682
683 /***
684 * Returns the response body of the HTTP method, if any, as an {@link InputStream}.
685 * If response body is not available, returns <tt>null</tt>
686 *
687 * @return The response body
688 *
689 * @throws IOException If an I/O (transport) problem occurs while obtaining the
690 * response body.
691 */
692 public InputStream getResponseBodyAsStream() throws IOException {
693 if (responseStream != null) {
694 return responseStream;
695 }
696 if (responseBody != null) {
697 InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
698 LOG.debug("re-creating response stream from byte array");
699 return byteResponseStream;
700 }
701 return null;
702 }
703
704 /***
705 * Returns the response body of the HTTP method, if any, as a {@link String}.
706 * If response body is not available or cannot be read, returns <tt>null</tt>
707 * The string conversion on the data is done using the character encoding specified
708 * in <tt>Content-Type</tt> header.
709 *
710 * @return The response body.
711 *
712 * @throws IOException If an I/O (transport) problem occurs while obtaining the
713 * response body.
714 */
715 public String getResponseBodyAsString() throws IOException {
716 byte[] rawdata = null;
717 if (responseAvailable()) {
718 rawdata = getResponseBody();
719 }
720 if (rawdata != null) {
721 return EncodingUtil.getString(rawdata, getResponseCharSet());
722 } else {
723 return null;
724 }
725 }
726
727 /***
728 * Returns an array of the response footers that the HTTP method currently has
729 * in the order in which they were read.
730 *
731 * @return an array of footers
732 */
733 public Header[] getResponseFooters() {
734 return getResponseTrailerHeaderGroup().getAllHeaders();
735 }
736
737 /***
738 * Gets the response footer associated with the given name.
739 * Footer name matching is case insensitive.
740 * <tt>null</tt> will be returned if either <i>footerName</i> is
741 * <tt>null</tt> or there is no matching footer for <i>footerName</i>
742 * or there are no footers available. If there are multiple footers
743 * with the same name, there values will be combined with the ',' separator
744 * as specified by RFC2616.
745 *
746 * @param footerName the footer name to match
747 * @return the matching footer
748 */
749 public Header getResponseFooter(String footerName) {
750 if (footerName == null) {
751 return null;
752 } else {
753 return getResponseTrailerHeaderGroup().getCondensedHeader(footerName);
754 }
755 }
756
757 /***
758 * Sets the response stream.
759 * @param responseStream The new response stream.
760 */
761 protected void setResponseStream(InputStream responseStream) {
762 this.responseStream = responseStream;
763 }
764
765 /***
766 * Returns a stream from which the body of the current response may be read.
767 * If the method has not yet been executed, if <code>responseBodyConsumed</code>
768 * has been called, or if the stream returned by a previous call has been closed,
769 * <code>null</code> will be returned.
770 *
771 * @return the current response stream
772 */
773 protected InputStream getResponseStream() {
774 return responseStream;
775 }
776
777 /***
778 * Returns the status text (or "reason phrase") associated with the latest
779 * response.
780 *
781 * @return The status text.
782 */
783 public String getStatusText() {
784 return statusLine.getReasonPhrase();
785 }
786
787 /***
788 * Defines how strictly HttpClient follows the HTTP protocol specification
789 * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
790 * implements the requirements of the specification, whereas in non-strict mode
791 * it attempts to mimic the exact behaviour of commonly used HTTP agents,
792 * which many HTTP servers expect.
793 *
794 * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
795 *
796 * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
797 * to exercise a more granular control over HTTP protocol strictness.
798 */
799 public void setStrictMode(boolean strictMode) {
800 if (strictMode) {
801 this.params.makeStrict();
802 } else {
803 this.params.makeLenient();
804 }
805 }
806
807 /***
808 * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
809 * to exercise a more granular control over HTTP protocol strictness.
810 *
811 * @return <tt>false</tt>
812 */
813 public boolean isStrictMode() {
814 return false;
815 }
816
817 /***
818 * Adds the specified request header, NOT overwriting any previous value.
819 * Note that header-name matching is case insensitive.
820 *
821 * @param headerName the header's name
822 * @param headerValue the header's value
823 */
824 public void addRequestHeader(String headerName, String headerValue) {
825 addRequestHeader(new Header(headerName, headerValue));
826 }
827
828 /***
829 * Tests if the connection should be force-closed when no longer needed.
830 *
831 * @return <code>true</code> if the connection must be closed
832 */
833 protected boolean isConnectionCloseForced() {
834 return this.connectionCloseForced;
835 }
836
837 /***
838 * Sets whether or not the connection should be force-closed when no longer
839 * needed. This value should only be set to <code>true</code> in abnormal
840 * circumstances, such as HTTP protocol violations.
841 *
842 * @param b <code>true</code> if the connection must be closed, <code>false</code>
843 * otherwise.
844 */
845 protected void setConnectionCloseForced(boolean b) {
846 if (LOG.isDebugEnabled()) {
847 LOG.debug("Force-close connection: " + b);
848 }
849 this.connectionCloseForced = b;
850 }
851
852 /***
853 * Tests if the connection should be closed after the method has been executed.
854 * The connection will be left open when using HTTP/1.1 or if <tt>Connection:
855 * keep-alive</tt> header was sent.
856 *
857 * @param conn the connection in question
858 *
859 * @return boolean true if we should close the connection.
860 */
861 protected boolean shouldCloseConnection(HttpConnection conn) {
862
863 if (isConnectionCloseForced()) {
864 LOG.debug("Should force-close connection.");
865 return true;
866 }
867
868 Header connectionHeader = null;
869
870 if (!conn.isTransparent()) {
871
872 connectionHeader = responseHeaders.getFirstHeader("proxy-connection");
873 }
874
875
876
877 if (connectionHeader == null) {
878 connectionHeader = responseHeaders.getFirstHeader("connection");
879 }
880 if (connectionHeader != null) {
881 if (connectionHeader.getValue().equalsIgnoreCase("close")) {
882 if (LOG.isDebugEnabled()) {
883 LOG.debug("Should close connection in response to "
884 + connectionHeader.toExternalForm());
885 }
886 return true;
887 } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) {
888 if (LOG.isDebugEnabled()) {
889 LOG.debug("Should NOT close connection in response to "
890 + connectionHeader.toExternalForm());
891 }
892 return false;
893 } else {
894 if (LOG.isDebugEnabled()) {
895 LOG.debug("Unknown directive: " + connectionHeader.toExternalForm());
896 }
897 }
898 }
899 LOG.debug("Resorting to protocol version default close connection policy");
900
901 if (this.effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
902 if (LOG.isDebugEnabled()) {
903 LOG.debug("Should NOT close connection, using " + this.effectiveVersion.toString());
904 }
905 } else {
906 if (LOG.isDebugEnabled()) {
907 LOG.debug("Should close connection, using " + this.effectiveVersion.toString());
908 }
909 }
910 return this.effectiveVersion.lessEquals(HttpVersion.HTTP_1_0);
911 }
912
913 /***
914 * Tests if the this method is ready to be executed.
915 *
916 * @param state the {@link HttpState state} information associated with this method
917 * @param conn the {@link HttpConnection connection} to be used
918 * @throws HttpException If the method is in invalid state.
919 */
920 private void checkExecuteConditions(HttpState state, HttpConnection conn)
921 throws HttpException {
922
923 if (state == null) {
924 throw new IllegalArgumentException("HttpState parameter may not be null");
925 }
926 if (conn == null) {
927 throw new IllegalArgumentException("HttpConnection parameter may not be null");
928 }
929 if (this.aborted) {
930 throw new IllegalStateException("Method has been aborted");
931 }
932 if (!validate()) {
933 throw new ProtocolException("HttpMethodBase object not valid");
934 }
935 }
936
937 /***
938 * Executes this method using the specified <code>HttpConnection</code> and
939 * <code>HttpState</code>.
940 *
941 * @param state {@link HttpState state} information to associate with this
942 * request. Must be non-null.
943 * @param conn the {@link HttpConnection connection} to used to execute
944 * this HTTP method. Must be non-null.
945 *
946 * @return the integer status code if one was obtained, or <tt>-1</tt>
947 *
948 * @throws IOException if an I/O (transport) error occurs
949 * @throws HttpException if a protocol exception occurs.
950 * @throws HttpRecoverableException if a recoverable transport error occurs.
951 * Usually this kind of exceptions can be recovered from by
952 * retrying the HTTP method
953 */
954 public int execute(HttpState state, HttpConnection conn)
955 throws HttpException, HttpRecoverableException, IOException {
956
957 LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
958
959
960
961 this.responseConnection = conn;
962
963 checkExecuteConditions(state, conn);
964 this.statusLine = null;
965 this.connectionCloseForced = false;
966
967 conn.setLastResponseInputStream(null);
968
969
970 if (this.effectiveVersion == null) {
971 this.effectiveVersion = this.params.getVersion();
972 }
973
974 boolean requestSent = false;
975 writeRequest(state, conn);
976 requestSent = true;
977 readResponse(state, conn);
978
979 used = true;
980
981 return statusLine.getStatusCode();
982 }
983
984 /***
985 * Aborts the execution of this method.
986 *
987 * @since 3.0
988 */
989 public void abort() {
990 if (this.aborted) {
991 return;
992 }
993 this.aborted = true;
994 HttpConnection conn = this.responseConnection;
995 if (conn != null) {
996 conn.close();
997 }
998 }
999
1000 /***
1001 * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1002 * but not {@link #recycle recycled}.
1003 *
1004 * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1005 */
1006 public boolean hasBeenUsed() {
1007 return used;
1008 }
1009
1010 /***
1011 * Recycles the HTTP method so that it can be used again.
1012 * Note that all of the instance variables will be reset
1013 * once this method has been called. This method will also
1014 * release the connection being used by this HTTP method.
1015 *
1016 * @see #releaseConnection()
1017 */
1018 public void recycle() {
1019 LOG.trace("enter HttpMethodBase.recycle()");
1020
1021 releaseConnection();
1022
1023 path = null;
1024 followRedirects = false;
1025 doAuthentication = true;
1026 queryString = null;
1027 getRequestHeaderGroup().clear();
1028 getResponseHeaderGroup().clear();
1029 getResponseTrailerHeaderGroup().clear();
1030 statusLine = null;
1031 effectiveVersion = null;
1032 aborted = false;
1033 used = false;
1034 params = new HttpMethodParams();
1035 responseBody = null;
1036 recoverableExceptionCount = 0;
1037 connectionCloseForced = false;
1038 hostAuthState.invalidate();
1039 proxyAuthState.invalidate();
1040 }
1041
1042 /***
1043 * Releases the connection being used by this HTTP method. In particular the
1044 * connection is used to read the response(if there is one) and will be held
1045 * until the response has been read. If the connection can be reused by other
1046 * HTTP methods it is NOT closed at this point.
1047 *
1048 * @since 2.0
1049 */
1050 public void releaseConnection() {
1051
1052 if (responseStream != null) {
1053 try {
1054
1055 responseStream.close();
1056 } catch (IOException e) {
1057
1058 ensureConnectionRelease();
1059 }
1060 } else {
1061
1062
1063
1064 ensureConnectionRelease();
1065 }
1066 }
1067
1068 /***
1069 * Remove the request header associated with the given name. Note that
1070 * header-name matching is case insensitive.
1071 *
1072 * @param headerName the header name
1073 */
1074 public void removeRequestHeader(String headerName) {
1075
1076 Header[] headers = getRequestHeaderGroup().getHeaders(headerName);
1077 for (int i = 0; i < headers.length; i++) {
1078 getRequestHeaderGroup().removeHeader(headers[i]);
1079 }
1080
1081 }
1082
1083 /***
1084 * Removes the given request header.
1085 *
1086 * @param header the header
1087 */
1088 public void removeRequestHeader(final Header header) {
1089 if (header == null) {
1090 return;
1091 }
1092 getRequestHeaderGroup().removeHeader(header);
1093 }
1094
1095
1096
1097 /***
1098 * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1099 *
1100 * @return This implementation always returns <tt>true</tt>.
1101 */
1102 public boolean validate() {
1103 return true;
1104 }
1105
1106
1107
1108 /***
1109 * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1110 * that match the given host, port and path.
1111 *
1112 * @param state the {@link HttpState state} information associated with this method
1113 * @param conn the {@link HttpConnection connection} used to execute
1114 * this HTTP method
1115 *
1116 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1117 * can be recovered from.
1118 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1119 * cannot be recovered from.
1120 */
1121 protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
1122 throws IOException, HttpException {
1123
1124 LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1125 + "HttpConnection)");
1126
1127 Header[] cookieheaders = getRequestHeaderGroup().getHeaders("Cookie");
1128 for (int i = 0; i < cookieheaders.length; i++) {
1129 Header cookieheader = cookieheaders[i];
1130 if (cookieheader.isAutogenerated()) {
1131 getRequestHeaderGroup().removeHeader(cookieheader);
1132 }
1133 }
1134
1135 CookieSpec matcher = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1136 Cookie[] cookies = matcher.match(conn.getHost(), conn.getPort(),
1137 getPath(), conn.isSecure(), state.getCookies());
1138 if ((cookies != null) && (cookies.length > 0)) {
1139 if (getParams().isParameterTrue(HttpMethodParams.SINGLE_COOKIE_HEADER)) {
1140
1141 Header header = matcher.formatCookieHeader(cookies);
1142 header.setAutogenerated(true);
1143 getRequestHeaderGroup().addHeader(header);
1144 } else {
1145
1146 for (int i = 0; i < cookies.length; i++) {
1147 Header header = matcher.formatCookieHeader(cookies[i]);
1148 header.setAutogenerated(true);
1149 getRequestHeaderGroup().addHeader(header);
1150 }
1151 }
1152 }
1153 }
1154
1155 /***
1156 * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1157 * header already exists.
1158 *
1159 * @param state the {@link HttpState state} information associated with this method
1160 * @param conn the {@link HttpConnection connection} used to execute
1161 * this HTTP method
1162 *
1163 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1164 * can be recovered from.
1165 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1166 * cannot be recovered from.
1167 */
1168 protected void addHostRequestHeader(HttpState state, HttpConnection conn)
1169 throws IOException, HttpException {
1170 LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1171 + "HttpConnection)");
1172
1173
1174
1175
1176
1177 String host = conn.getVirtualHost();
1178 if (host != null) {
1179 LOG.debug("Using virtual host name: " + host);
1180 } else {
1181 host = conn.getHost();
1182 }
1183 int port = conn.getPort();
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193 if (LOG.isDebugEnabled()) {
1194 LOG.debug("Adding Host request header");
1195 }
1196
1197
1198 if (conn.getProtocol().getDefaultPort() != port) {
1199 host += (":" + port);
1200 }
1201
1202 setRequestHeader("Host", host);
1203 }
1204
1205 /***
1206 * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when
1207 * communicating via a proxy server.
1208 *
1209 * @param state the {@link HttpState state} information associated with this method
1210 * @param conn the {@link HttpConnection connection} used to execute
1211 * this HTTP method
1212 *
1213 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1214 * can be recovered from.
1215 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1216 * cannot be recovered from.
1217 */
1218 protected void addProxyConnectionHeader(HttpState state,
1219 HttpConnection conn)
1220 throws IOException, HttpException {
1221 LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1222 + "HttpState, HttpConnection)");
1223 if (!conn.isTransparent()) {
1224 setRequestHeader("Proxy-Connection", "Keep-Alive");
1225 }
1226 }
1227
1228 /***
1229 * Generates all the required request {@link Header header}s
1230 * to be submitted via the given {@link HttpConnection connection}.
1231 *
1232 * <p>
1233 * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1234 * <tt>Cookie</tt>, <tt>Authorization</tt>, <tt>Proxy-Authorization</tt>
1235 * and <tt>Proxy-Connection</tt> headers, when appropriate.
1236 * </p>
1237 *
1238 * <p>
1239 * Subclasses may want to override this method to to add additional
1240 * headers, and may choose to invoke this implementation (via
1241 * <tt>super</tt>) to add the "standard" headers.
1242 * </p>
1243 *
1244 * @param state the {@link HttpState state} information associated with this method
1245 * @param conn the {@link HttpConnection connection} used to execute
1246 * this HTTP method
1247 *
1248 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1249 * can be recovered from.
1250 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1251 * cannot be recovered from.
1252 *
1253 * @see #writeRequestHeaders
1254 */
1255 protected void addRequestHeaders(HttpState state, HttpConnection conn)
1256 throws IOException, HttpException {
1257 LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1258 + "HttpConnection)");
1259
1260 addUserAgentRequestHeader(state, conn);
1261 addHostRequestHeader(state, conn);
1262 addCookieRequestHeader(state, conn);
1263 addProxyConnectionHeader(state, conn);
1264 }
1265
1266 /***
1267 * Generates default <tt>User-Agent</tt> request header, as long as no
1268 * <tt>User-Agent</tt> request header already exists.
1269 *
1270 * @param state the {@link HttpState state} information associated with this method
1271 * @param conn the {@link HttpConnection connection} used to execute
1272 * this HTTP method
1273 *
1274 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1275 * can be recovered from.
1276 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1277 * cannot be recovered from.
1278 */
1279 protected void addUserAgentRequestHeader(HttpState state,
1280 HttpConnection conn)
1281 throws IOException, HttpException {
1282 LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1283 + "HttpConnection)");
1284
1285 if (getRequestHeader("User-Agent") == null) {
1286 String agent = (String)getParams().getParameter(HttpMethodParams.USER_AGENT);
1287 if (agent == null) {
1288 agent = "Jakarta Commons-HttpClient";
1289 }
1290 setRequestHeader("User-Agent", agent);
1291 }
1292 }
1293
1294 /***
1295 * Throws an {@link IllegalStateException} if the HTTP method has been already
1296 * {@link #execute executed}, but not {@link #recycle recycled}.
1297 *
1298 * @throws IllegalStateException if the method has been used and not
1299 * recycled
1300 */
1301 protected void checkNotUsed() throws IllegalStateException {
1302 if (used) {
1303 throw new IllegalStateException("Already used.");
1304 }
1305 }
1306
1307 /***
1308 * Throws an {@link IllegalStateException} if the HTTP method has not been
1309 * {@link #execute executed} since last {@link #recycle recycle}.
1310 *
1311 *
1312 * @throws IllegalStateException if not used
1313 */
1314 protected void checkUsed() throws IllegalStateException {
1315 if (!used) {
1316 throw new IllegalStateException("Not Used.");
1317 }
1318 }
1319
1320
1321
1322 /***
1323 * Generates HTTP request line according to the specified attributes.
1324 *
1325 * @param connection the {@link HttpConnection connection} used to execute
1326 * this HTTP method
1327 * @param name the method name generate a request for
1328 * @param requestPath the path string for the request
1329 * @param query the query string for the request
1330 * @param version the protocol version to use (e.g. HTTP/1.0)
1331 *
1332 * @return HTTP request line
1333 */
1334 protected static String generateRequestLine(HttpConnection connection,
1335 String name, String requestPath, String query, String version) {
1336 LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1337 + "String, String, String, String)");
1338
1339 StringBuffer buf = new StringBuffer();
1340
1341 buf.append(name);
1342 buf.append(" ");
1343
1344 if (!connection.isTransparent()) {
1345 Protocol protocol = connection.getProtocol();
1346 buf.append(protocol.getScheme().toLowerCase());
1347 buf.append("://");
1348 buf.append(connection.getHost());
1349 if ((connection.getPort() != -1)
1350 && (connection.getPort() != protocol.getDefaultPort())
1351 ) {
1352 buf.append(":");
1353 buf.append(connection.getPort());
1354 }
1355 }
1356
1357 if (requestPath == null) {
1358 buf.append("/");
1359 } else {
1360 if (!connection.isTransparent() && !requestPath.startsWith("/")) {
1361 buf.append("/");
1362 }
1363 buf.append(requestPath);
1364 }
1365
1366 if (query != null) {
1367 if (query.indexOf("?") != 0) {
1368 buf.append("?");
1369 }
1370 buf.append(query);
1371 }
1372
1373 buf.append(" ");
1374 buf.append(version);
1375 buf.append("\r\n");
1376
1377 return buf.toString();
1378 }
1379
1380 /***
1381 * This method is invoked immediately after
1382 * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by
1383 * sub-classes in order to provide custom body processing.
1384 *
1385 * <p>
1386 * This implementation does nothing.
1387 * </p>
1388 *
1389 * @param state the {@link HttpState state} information associated with this method
1390 * @param conn the {@link HttpConnection connection} used to execute
1391 * this HTTP method
1392 *
1393 * @see #readResponse
1394 * @see #readResponseBody
1395 */
1396 protected void processResponseBody(HttpState state, HttpConnection conn) {
1397 }
1398
1399 /***
1400 * This method is invoked immediately after
1401 * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by
1402 * sub-classes in order to provide custom response headers processing.
1403
1404 * <p>
1405 * This implementation will handle the <tt>Set-Cookie</tt> and
1406 * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
1407 * the given {@link HttpState}.
1408 * </p>
1409 *
1410 * @param state the {@link HttpState state} information associated with this method
1411 * @param conn the {@link HttpConnection connection} used to execute
1412 * this HTTP method
1413 *
1414 * @see #readResponse
1415 * @see #readResponseHeaders
1416 */
1417 protected void processResponseHeaders(HttpState state,
1418 HttpConnection conn) {
1419 LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
1420 + "HttpConnection)");
1421
1422 Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2");
1423
1424
1425 if (headers.length == 0) {
1426 headers = getResponseHeaderGroup().getHeaders("set-cookie");
1427 }
1428
1429 CookieSpec parser = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1430 for (int i = 0; i < headers.length; i++) {
1431 Header header = headers[i];
1432 Cookie[] cookies = null;
1433 try {
1434 cookies = parser.parse(
1435 conn.getHost(),
1436 conn.getPort(),
1437 getPath(),
1438 conn.isSecure(),
1439 header);
1440 } catch (MalformedCookieException e) {
1441 if (LOG.isWarnEnabled()) {
1442 LOG.warn("Invalid cookie header: \""
1443 + header.getValue()
1444 + "\". " + e.getMessage());
1445 }
1446 }
1447 if (cookies != null) {
1448 for (int j = 0; j < cookies.length; j++) {
1449 Cookie cookie = cookies[j];
1450 try {
1451 parser.validate(
1452 conn.getHost(),
1453 conn.getPort(),
1454 getPath(),
1455 conn.isSecure(),
1456 cookie);
1457 state.addCookie(cookie);
1458 if (LOG.isDebugEnabled()) {
1459 LOG.debug("Cookie accepted: \""
1460 + parser.formatCookie(cookie) + "\"");
1461 }
1462 } catch (MalformedCookieException e) {
1463 if (LOG.isWarnEnabled()) {
1464 LOG.warn("Cookie rejected: \"" + parser.formatCookie(cookie)
1465 + "\". " + e.getMessage());
1466 }
1467 }
1468 }
1469 }
1470 }
1471 }
1472
1473 /***
1474 * This method is invoked immediately after
1475 * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by
1476 * sub-classes in order to provide custom response status line processing.
1477 *
1478 * @param state the {@link HttpState state} information associated with this method
1479 * @param conn the {@link HttpConnection connection} used to execute
1480 * this HTTP method
1481 *
1482 * @see #readResponse
1483 * @see #readStatusLine
1484 */
1485 protected void processStatusLine(HttpState state, HttpConnection conn) {
1486 }
1487
1488 /***
1489 * Reads the response from the given {@link HttpConnection connection}.
1490 *
1491 * <p>
1492 * The response is processed as the following sequence of actions:
1493 *
1494 * <ol>
1495 * <li>
1496 * {@link #readStatusLine(HttpState,HttpConnection)} is
1497 * invoked to read the request line.
1498 * </li>
1499 * <li>
1500 * {@link #processStatusLine(HttpState,HttpConnection)}
1501 * is invoked, allowing the method to process the status line if
1502 * desired.
1503 * </li>
1504 * <li>
1505 * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read
1506 * the associated headers.
1507 * </li>
1508 * <li>
1509 * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing
1510 * the method to process the headers if desired.
1511 * </li>
1512 * <li>
1513 * {@link #readResponseBody(HttpState,HttpConnection)} is
1514 * invoked to read the associated body (if any).
1515 * </li>
1516 * <li>
1517 * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the
1518 * method to process the response body if desired.
1519 * </li>
1520 * </ol>
1521 *
1522 * Subclasses may want to override one or more of the above methods to to
1523 * customize the processing. (Or they may choose to override this method
1524 * if dramatically different processing is required.)
1525 * </p>
1526 *
1527 * @param state the {@link HttpState state} information associated with this method
1528 * @param conn the {@link HttpConnection connection} used to execute
1529 * this HTTP method
1530 *
1531 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1532 * can be recovered from.
1533 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1534 * cannot be recovered from.
1535 */
1536 protected void readResponse(HttpState state, HttpConnection conn)
1537 throws IOException, HttpException {
1538 LOG.trace(
1539 "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
1540
1541
1542 while (this.statusLine == null) {
1543 readStatusLine(state, conn);
1544 processStatusLine(state, conn);
1545 readResponseHeaders(state, conn);
1546 processResponseHeaders(state, conn);
1547
1548 int status = this.statusLine.getStatusCode();
1549 if ((status >= 100) && (status < 200)) {
1550 if (LOG.isInfoEnabled()) {
1551 LOG.info("Discarding unexpected response: " + this.statusLine.toString());
1552 }
1553 this.statusLine = null;
1554 }
1555 }
1556 readResponseBody(state, conn);
1557 processResponseBody(state, conn);
1558 }
1559
1560 /***
1561 * Read the response body from the given {@link HttpConnection}.
1562 *
1563 * <p>
1564 * The current implementation wraps the socket level stream with
1565 * an appropriate stream for the type of response (chunked, content-length,
1566 * or auto-close). If there is no response body, the connection associated
1567 * with the request will be returned to the connection manager.
1568 * </p>
1569 *
1570 * <p>
1571 * Subclasses may want to override this method to to customize the
1572 * processing.
1573 * </p>
1574 *
1575 * @param state the {@link HttpState state} information associated with this method
1576 * @param conn the {@link HttpConnection connection} used to execute
1577 * this HTTP method
1578 *
1579 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1580 * can be recovered from.
1581 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1582 * cannot be recovered from.
1583 *
1584 * @see #readResponse
1585 * @see #processResponseBody
1586 */
1587 protected void readResponseBody(HttpState state, HttpConnection conn)
1588 throws IOException, HttpException {
1589 LOG.trace(
1590 "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1591
1592
1593 InputStream stream = readResponseBody(conn);
1594 if (stream == null) {
1595
1596 responseBodyConsumed();
1597 } else {
1598 conn.setLastResponseInputStream(stream);
1599 setResponseStream(stream);
1600 }
1601 }
1602
1603 /***
1604 * Returns the response body as an {@link InputStream input stream}
1605 * corresponding to the values of the <tt>Content-Length</tt> and
1606 * <tt>Transfer-Encoding</tt> headers. If no response body is available
1607 * returns <tt>null</tt>.
1608 * <p>
1609 *
1610 * @see #readResponse
1611 * @see #processResponseBody
1612 *
1613 * @param conn the {@link HttpConnection connection} used to execute
1614 * this HTTP method
1615 *
1616 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1617 * can be recovered from.
1618 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1619 * cannot be recovered from.
1620 */
1621 private InputStream readResponseBody(HttpConnection conn)
1622 throws HttpException, IOException {
1623
1624 LOG.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1625
1626 responseBody = null;
1627 InputStream is = conn.getResponseInputStream();
1628 if (Wire.traceEnabled()) {
1629 is = new WireLogInputStream(is);
1630 }
1631 InputStream result = null;
1632 Header transferEncodingHeader = responseHeaders.getFirstHeader("Transfer-Encoding");
1633
1634
1635 if (transferEncodingHeader != null) {
1636
1637 String transferEncoding = transferEncodingHeader.getValue();
1638 if (!"chunked".equalsIgnoreCase(transferEncoding)
1639 && !"identity".equalsIgnoreCase(transferEncoding)) {
1640 if (LOG.isWarnEnabled()) {
1641 LOG.warn("Unsupported transfer encoding: " + transferEncoding);
1642 }
1643 }
1644 HeaderElement[] encodings = transferEncodingHeader.getElements();
1645
1646
1647 int len = encodings.length;
1648 if ((len > 0) && ("chunked".equalsIgnoreCase(encodings[len - 1].getName()))) {
1649
1650 if (conn.isResponseAvailable(conn.getParams().getSoTimeout())) {
1651 result = new ChunkedInputStream(is, this);
1652 } else {
1653 if (getParams().isParameterTrue(HttpMethodParams.STRICT_TRANSFER_ENCODING)) {
1654 throw new ProtocolException("Chunk-encoded body declared but not sent");
1655 } else {
1656 LOG.warn("Chunk-encoded body missing");
1657 }
1658 }
1659 } else {
1660 if (LOG.isWarnEnabled()) {
1661 LOG.warn("Transfer-Encoding is set but does not contain \"chunked\": "
1662 + transferEncoding);
1663 }
1664
1665
1666 setConnectionCloseForced(true);
1667 result = is;
1668 }
1669 } else {
1670 long expectedLength = getResponseContentLength();
1671 if (expectedLength == -1) {
1672 if (canResponseHaveBody(statusLine.getStatusCode())) {
1673 Header connectionHeader = responseHeaders.getFirstHeader("Connection");
1674 String connectionDirective = null;
1675 if (connectionHeader != null) {
1676 connectionDirective = connectionHeader.getValue();
1677 }
1678 if (!"close".equalsIgnoreCase(connectionDirective)) {
1679 LOG.warn("Response content length is not known");
1680 setConnectionCloseForced(true);
1681 }
1682 result = is;
1683 }
1684 } else {
1685 result = new ContentLengthInputStream(is, expectedLength);
1686 }
1687 }
1688
1689
1690
1691 if (result != null) {
1692
1693 result = new AutoCloseInputStream(
1694 result,
1695 new ResponseConsumedWatcher() {
1696 public void responseConsumed() {
1697 responseBodyConsumed();
1698 }
1699 }
1700 );
1701 }
1702
1703 return result;
1704 }
1705
1706 /***
1707 * Reads the response headers from the given {@link HttpConnection connection}.
1708 *
1709 * <p>
1710 * Subclasses may want to override this method to to customize the
1711 * processing.
1712 * </p>
1713 *
1714 * <p>
1715 * "It must be possible to combine the multiple header fields into one
1716 * "field-name: field-value" pair, without changing the semantics of the
1717 * message, by appending each subsequent field-value to the first, each
1718 * separated by a comma." - HTTP/1.0 (4.3)
1719 * </p>
1720 *
1721 * @param state the {@link HttpState state} information associated with this method
1722 * @param conn the {@link HttpConnection connection} used to execute
1723 * this HTTP method
1724 *
1725 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1726 * can be recovered from.
1727 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1728 * cannot be recovered from.
1729 *
1730 * @see #readResponse
1731 * @see #processResponseHeaders
1732 */
1733 protected void readResponseHeaders(HttpState state, HttpConnection conn)
1734 throws IOException, HttpException {
1735 LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
1736 + "HttpConnection)");
1737
1738 getResponseHeaderGroup().clear();
1739
1740 Header[] headers = HttpParser.parseHeaders(
1741 conn.getResponseInputStream(), getParams().getHttpElementCharset());
1742 if (Wire.enabled()) {
1743 for (int i = 0; i < headers.length; i++) {
1744 Wire.input(headers[i].toExternalForm());
1745 }
1746 }
1747 getResponseHeaderGroup().setHeaders(headers);
1748 }
1749
1750 /***
1751 * Read the status line from the given {@link HttpConnection}, setting my
1752 * {@link #getStatusCode status code} and {@link #getStatusText status
1753 * text}.
1754 *
1755 * <p>
1756 * Subclasses may want to override this method to to customize the
1757 * processing.
1758 * </p>
1759 *
1760 * @param state the {@link HttpState state} information associated with this method
1761 * @param conn the {@link HttpConnection connection} used to execute
1762 * this HTTP method
1763 *
1764 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1765 * can be recovered from.
1766 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1767 * cannot be recovered from.
1768 *
1769 * @see StatusLine
1770 */
1771 protected void readStatusLine(HttpState state, HttpConnection conn)
1772 throws IOException, HttpRecoverableException, HttpException {
1773 LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
1774
1775 final int maxGarbageLines = getParams().
1776 getIntParameter(HttpMethodParams.STATUS_LINE_GARBAGE_LIMIT, Integer.MAX_VALUE);
1777
1778
1779 int count = 0;
1780 String s;
1781 do {
1782 s = conn.readLine(getParams().getHttpElementCharset());
1783 if (Wire.enabled()) {
1784 Wire.input(s + "\r\n");
1785 }
1786 if (s != null && StatusLine.startsWithHTTP(s)) {
1787
1788 break;
1789 } else if (s == null || count >= maxGarbageLines) {
1790
1791 throw new HttpRecoverableException("Error in parsing the status "
1792 + " line from the response: unable to find line starting with"
1793 + " \"HTTP\"");
1794 }
1795 count++;
1796 } while(true);
1797
1798
1799 statusLine = new StatusLine(s);
1800
1801
1802 String versionStr = statusLine.getHttpVersion();
1803 if (getParams().isParameterFalse(HttpMethodParams.UNAMBIGUOUS_STATUS_LINE)
1804 && versionStr.equals("HTTP")) {
1805 getParams().setVersion(HttpVersion.HTTP_1_0);
1806 if (LOG.isWarnEnabled()) {
1807 LOG.warn("Ambiguous status line (HTTP protocol version missing):" +
1808 statusLine.toString());
1809 }
1810 } else {
1811 this.effectiveVersion = HttpVersion.parse(versionStr);
1812 }
1813
1814 }
1815
1816
1817
1818 /***
1819 * <p>
1820 * Sends the request via the given {@link HttpConnection connection}.
1821 * </p>
1822 *
1823 * <p>
1824 * The request is written as the following sequence of actions:
1825 * </p>
1826 *
1827 * <ol>
1828 * <li>
1829 * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to
1830 * write the request line.
1831 * </li>
1832 * <li>
1833 * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked
1834 * to write the associated headers.
1835 * </li>
1836 * <li>
1837 * <tt>\r\n</tt> is sent to close the head part of the request.
1838 * </li>
1839 * <li>
1840 * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to
1841 * write the body part of the request.
1842 * </li>
1843 * </ol>
1844 *
1845 * <p>
1846 * Subclasses may want to override one or more of the above methods to to
1847 * customize the processing. (Or they may choose to override this method
1848 * if dramatically different processing is required.)
1849 * </p>
1850 *
1851 * @param state the {@link HttpState state} information associated with this method
1852 * @param conn the {@link HttpConnection connection} used to execute
1853 * this HTTP method
1854 *
1855 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1856 * can be recovered from.
1857 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1858 * cannot be recovered from.
1859 */
1860 protected void writeRequest(HttpState state, HttpConnection conn)
1861 throws IOException, HttpException {
1862 LOG.trace(
1863 "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
1864 writeRequestLine(state, conn);
1865 writeRequestHeaders(state, conn);
1866 conn.writeLine();
1867
1868 conn.flushRequestOutputStream();
1869 if (Wire.enabled()) {
1870 Wire.output("\r\n");
1871 }
1872
1873 HttpVersion ver = getParams().getVersion();
1874 Header expectheader = getRequestHeader("Expect");
1875 String expectvalue = null;
1876 if (expectheader != null) {
1877 expectvalue = expectheader.getValue();
1878 }
1879 if ((expectvalue != null)
1880 && (expectvalue.compareToIgnoreCase("100-continue") == 0)) {
1881 if (ver.greaterEquals(HttpVersion.HTTP_1_1)) {
1882 int readTimeout = conn.getParams().getSoTimeout();
1883 try {
1884 conn.setSocketTimeout(RESPONSE_WAIT_TIME_MS);
1885 readStatusLine(state, conn);
1886 processStatusLine(state, conn);
1887 readResponseHeaders(state, conn);
1888 processResponseHeaders(state, conn);
1889
1890 if (this.statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
1891
1892 this.statusLine = null;
1893 LOG.debug("OK to continue received");
1894 } else {
1895 return;
1896 }
1897 } catch (IOTimeoutException e) {
1898
1899
1900
1901 removeRequestHeader("Expect");
1902 LOG.info("100 (continue) read timeout. Resume sending the request");
1903 } finally {
1904 conn.setSocketTimeout(readTimeout);
1905 }
1906
1907 } else {
1908 removeRequestHeader("Expect");
1909 LOG.info("'Expect: 100-continue' handshake is only supported by "
1910 + "HTTP/1.1 or higher");
1911 }
1912 }
1913
1914 writeRequestBody(state, conn);
1915
1916 conn.flushRequestOutputStream();
1917 }
1918
1919 /***
1920 * Writes the request body to the given {@link HttpConnection connection}.
1921 *
1922 * <p>
1923 * This method should return <tt>true</tt> if the request body was actually
1924 * sent (or is empty), or <tt>false</tt> if it could not be sent for some
1925 * reason.
1926 * </p>
1927 *
1928 * <p>
1929 * This implementation writes nothing and returns <tt>true</tt>.
1930 * </p>
1931 *
1932 * @param state the {@link HttpState state} information associated with this method
1933 * @param conn the {@link HttpConnection connection} used to execute
1934 * this HTTP method
1935 *
1936 * @return <tt>true</tt>
1937 *
1938 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1939 * can be recovered from.
1940 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1941 * cannot be recovered from.
1942 */
1943 protected boolean writeRequestBody(HttpState state, HttpConnection conn)
1944 throws IOException, HttpException {
1945 return true;
1946 }
1947
1948 /***
1949 * Writes the request headers to the given {@link HttpConnection connection}.
1950 *
1951 * <p>
1952 * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)},
1953 * and then writes each header to the request stream.
1954 * </p>
1955 *
1956 * <p>
1957 * Subclasses may want to override this method to to customize the
1958 * processing.
1959 * </p>
1960 *
1961 * @param state the {@link HttpState state} information associated with this method
1962 * @param conn the {@link HttpConnection connection} used to execute
1963 * this HTTP method
1964 *
1965 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1966 * can be recovered from.
1967 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1968 * cannot be recovered from.
1969 *
1970 * @see #addRequestHeaders
1971 * @see #getRequestHeaders
1972 */
1973 protected void writeRequestHeaders(HttpState state, HttpConnection conn)
1974 throws IOException, HttpException {
1975 LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
1976 + "HttpConnection)");
1977 addRequestHeaders(state, conn);
1978
1979 String charset = getParams().getHttpElementCharset();
1980
1981 Header[] headers = getRequestHeaders();
1982 for (int i = 0; i < headers.length; i++) {
1983 String s = headers[i].toExternalForm();
1984 if (Wire.enabled()) {
1985 Wire.output(s);
1986 }
1987 conn.print(s, charset);
1988 }
1989 }
1990
1991 /***
1992 * Writes the request line to the given {@link HttpConnection connection}.
1993 *
1994 * <p>
1995 * Subclasses may want to override this method to to customize the
1996 * processing.
1997 * </p>
1998 *
1999 * @param state the {@link HttpState state} information associated with this method
2000 * @param conn the {@link HttpConnection connection} used to execute
2001 * this HTTP method
2002 *
2003 * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2004 * can be recovered from.
2005 * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2006 * cannot be recovered from.
2007 *
2008 * @see #generateRequestLine
2009 */
2010 protected void writeRequestLine(HttpState state, HttpConnection conn)
2011 throws IOException, HttpException {
2012 LOG.trace(
2013 "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2014 String requestLine = getRequestLine(conn);
2015 if (Wire.enabled()) {
2016 Wire.output(requestLine);
2017 }
2018 conn.print(requestLine, getParams().getHttpElementCharset());
2019 }
2020
2021 /***
2022 * Returns the request line.
2023 *
2024 * @param conn the {@link HttpConnection connection} used to execute
2025 * this HTTP method
2026 *
2027 * @return The request line.
2028 */
2029 private String getRequestLine(HttpConnection conn) {
2030 return HttpMethodBase.generateRequestLine(conn, getName(),
2031 getPath(), getQueryString(), this.effectiveVersion.toString());
2032 }
2033
2034 /***
2035 * Returns {@link HttpMethodParams HTTP protocol parameters} associated with this method.
2036 *
2037 * @return HTTP parameters.
2038 *
2039 * @since 3.0
2040 */
2041 public HttpMethodParams getParams() {
2042 return this.params;
2043 }
2044
2045 /***
2046 * Assigns {@link HttpMethodParams HTTP protocol parameters} for this method.
2047 *
2048 * @since 3.0
2049 *
2050 * @see HttpMethodParams
2051 */
2052 public void setParams(final HttpMethodParams params) {
2053 if (params == null) {
2054 throw new IllegalArgumentException("Parameters may not be null");
2055 }
2056 this.params = params;
2057 }
2058
2059 /***
2060 * Returns the HTTP version used with this method (may be <tt>null</tt>
2061 * if undefined, that is, the method has not been executed)
2062 *
2063 * @return HTTP version.
2064 *
2065 * @since 3.0
2066 */
2067 public HttpVersion getEffectiveVersion() {
2068 return this.effectiveVersion;
2069 }
2070
2071 /***
2072 * Per RFC 2616 section 4.3, some response can never contain a message
2073 * body.
2074 *
2075 * @param status - the HTTP status code
2076 *
2077 * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not
2078 * contain a message body
2079 */
2080 private static boolean canResponseHaveBody(int status) {
2081 LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)");
2082
2083 boolean result = true;
2084
2085 if ((status >= 100 && status <= 199) || (status == 204)
2086 || (status == 304)) {
2087 result = false;
2088 }
2089
2090 return result;
2091 }
2092
2093 /***
2094 * Returns proxy authentication realm, if it has been used during authentication process.
2095 * Otherwise returns <tt>null</tt>.
2096 *
2097 * @return proxy authentication realm
2098 *
2099 * @deprecated use #getProxyAuthState()
2100 */
2101 public String getProxyAuthenticationRealm() {
2102 return this.proxyAuthState.getRealm();
2103 }
2104
2105 /***
2106 * Returns authentication realm, if it has been used during authentication process.
2107 * Otherwise returns <tt>null</tt>.
2108 *
2109 * @return authentication realm
2110 *
2111 * @deprecated use #getHostAuthState()
2112 */
2113 public String getAuthenticationRealm() {
2114 return this.hostAuthState.getRealm();
2115 }
2116
2117 /***
2118 * Returns the character set from the <tt>Content-Type</tt> header.
2119 *
2120 * @param contentheader The content header.
2121 * @return String The character set.
2122 */
2123 protected String getContentCharSet(Header contentheader) {
2124 LOG.trace("enter getContentCharSet( Header contentheader )");
2125 String charset = null;
2126 if (contentheader != null) {
2127 HeaderElement values[] = contentheader.getElements();
2128
2129
2130 if (values.length == 1) {
2131 NameValuePair param = values[0].getParameterByName("charset");
2132 if (param != null) {
2133
2134
2135 charset = param.getValue();
2136 }
2137 }
2138 }
2139 if (charset == null) {
2140 charset = getParams().getContentCharset();
2141 if (LOG.isDebugEnabled()) {
2142 LOG.debug("Default charset used: " + charset);
2143 }
2144 }
2145 return charset;
2146 }
2147
2148
2149 /***
2150 * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
2151 *
2152 * @return String The character set.
2153 */
2154 public String getRequestCharSet() {
2155 return getContentCharSet(getRequestHeader("Content-Type"));
2156 }
2157
2158
2159 /***
2160 * Returns the character encoding of the response from the <tt>Content-Type</tt> header.
2161 *
2162 * @return String The character set.
2163 */
2164 public String getResponseCharSet() {
2165 return getContentCharSet(getResponseHeader("Content-Type"));
2166 }
2167
2168 /***
2169 * @deprecated no longer used
2170 *
2171 * Returns the number of "recoverable" exceptions thrown and handled, to
2172 * allow for monitoring the quality of the connection.
2173 *
2174 * @return The number of recoverable exceptions handled by the method.
2175 */
2176 public int getRecoverableExceptionCount() {
2177 return recoverableExceptionCount;
2178 }
2179
2180 /***
2181 * A response has been consumed.
2182 *
2183 * <p>The default behavior for this class is to check to see if the connection
2184 * should be closed, and close if need be, and to ensure that the connection
2185 * is returned to the connection manager - if and only if we are not still
2186 * inside the execute call.</p>
2187 *
2188 */
2189 protected void responseBodyConsumed() {
2190
2191
2192
2193 responseStream = null;
2194 if (responseConnection != null) {
2195 responseConnection.setLastResponseInputStream(null);
2196
2197
2198
2199
2200
2201 try {
2202 if(responseConnection.isResponseAvailable()) {
2203 boolean logExtraInput =
2204 getParams().isParameterTrue(HttpMethodParams.WARN_EXTRA_INPUT);
2205
2206 if(logExtraInput) {
2207 LOG.warn("Extra response data detected - closing connection");
2208 }
2209 setConnectionCloseForced(true);
2210 }
2211 }
2212 catch (IOException e) {
2213 LOG.info(e.getMessage());
2214 responseConnection.close();
2215 }
2216 if (shouldCloseConnection(responseConnection)) {
2217 responseConnection.close();
2218 }
2219 }
2220 this.connectionCloseForced = false;
2221 ensureConnectionRelease();
2222 }
2223
2224 /***
2225 * Insure that the connection is released back to the pool.
2226 */
2227 private void ensureConnectionRelease() {
2228 if (responseConnection != null) {
2229 responseConnection.releaseConnection();
2230 responseConnection = null;
2231 }
2232 }
2233
2234 /***
2235 * Returns the {@link HostConfiguration host configuration}.
2236 *
2237 * @return the host configuration
2238 */
2239 public HostConfiguration getHostConfiguration() {
2240 return hostConfiguration;
2241 }
2242
2243 /***
2244 * Sets the {@link HostConfiguration host configuration}.
2245 *
2246 * @param hostConfiguration The hostConfiguration to set
2247 */
2248 public void setHostConfiguration(HostConfiguration hostConfiguration) {
2249 this.hostConfiguration = hostConfiguration;
2250 }
2251
2252 /***
2253 * Returns the {@link MethodRetryHandler retry handler} for this HTTP method
2254 *
2255 * @return the methodRetryHandler
2256 */
2257 public MethodRetryHandler getMethodRetryHandler() {
2258
2259 if (methodRetryHandler == null) {
2260 methodRetryHandler = new DefaultMethodRetryHandler();
2261 }
2262
2263 return methodRetryHandler;
2264 }
2265
2266 /***
2267 * Sets the {@link MethodRetryHandler retry handler} for this HTTP method
2268 *
2269 * @param handler the methodRetryHandler to use when this method executed
2270 */
2271 public void setMethodRetryHandler(MethodRetryHandler handler) {
2272 methodRetryHandler = handler;
2273 }
2274
2275 /***
2276 * This method is a dirty hack intended to work around
2277 * current (2.0) design flaw that prevents the user from
2278 * obtaining correct status code, headers and response body from the
2279 * preceding HTTP CONNECT method.
2280 *
2281 * TODO: Remove this crap as soon as possible
2282 */
2283 void fakeResponse(
2284 StatusLine statusline,
2285 HeaderGroup responseheaders,
2286 InputStream responseStream
2287 ) {
2288
2289 this.used = true;
2290 this.statusLine = statusline;
2291 this.responseHeaders = responseheaders;
2292 this.responseBody = null;
2293 this.responseStream = responseStream;
2294 }
2295
2296 /***
2297 * Returns the target host {@link AuthState authentication state}
2298 *
2299 * @return host authentication state
2300 *
2301 * @since 3.0
2302 */
2303 public AuthState getHostAuthState() {
2304 return this.hostAuthState;
2305 }
2306
2307 /***
2308 * Returns the proxy {@link AuthState authentication state}
2309 *
2310 * @return host authentication state
2311 *
2312 * @since 3.0
2313 */
2314 public AuthState getProxyAuthState() {
2315 return this.proxyAuthState;
2316 }
2317
2318 /***
2319 * Tests whether the execution of this method has been aborted
2320 *
2321 * @return <tt>true</tt> if the execution of this method has been aborted,
2322 * <tt>false</tt> otherwise
2323 *
2324 * @since 3.0
2325 */
2326 public boolean isAborted() {
2327 return this.aborted;
2328 }
2329 }