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