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