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