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