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