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