Home » commons-httpclient-3.1-src » org.apache.commons » httpclient » [javadoc | source]
    1   /*
    2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.34 2005/01/14 19:40:39 olegk Exp $
    3    * $Revision: 486658 $
    4    * $Date: 2006-12-13 15:05:50 +0100 (Wed, 13 Dec 2006) $
    5    *
    6    * ====================================================================
    7    *
    8    *  Licensed to the Apache Software Foundation (ASF) under one or more
    9    *  contributor license agreements.  See the NOTICE file distributed with
   10    *  this work for additional information regarding copyright ownership.
   11    *  The ASF licenses this file to You under the Apache License, Version 2.0
   12    *  (the "License"); you may not use this file except in compliance with
   13    *  the License.  You may obtain a copy of the License at
   14    *
   15    *      http://www.apache.org/licenses/LICENSE-2.0
   16    *
   17    *  Unless required by applicable law or agreed to in writing, software
   18    *  distributed under the License is distributed on an "AS IS" BASIS,
   19    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   20    *  See the License for the specific language governing permissions and
   21    *  limitations under the License.
   22    * ====================================================================
   23    *
   24    * This software consists of voluntary contributions made by many
   25    * individuals on behalf of the Apache Software Foundation.  For more
   26    * information on the Apache Software Foundation, please see
   27    * <http://www.apache.org/>.
   28    *
   29    */
   30   
   31   package org.apache.commons.httpclient;
   32   
   33   import java.io.IOException;
   34   import java.util.Collection;
   35   import java.util.HashSet;
   36   import java.util.Iterator;
   37   import java.util.Map;
   38   import java.util.Set;
   39   
   40   import org.apache.commons.httpclient.auth.AuthChallengeException;
   41   import org.apache.commons.httpclient.auth.AuthChallengeParser;
   42   import org.apache.commons.httpclient.auth.AuthChallengeProcessor;
   43   import org.apache.commons.httpclient.auth.AuthScheme;
   44   import org.apache.commons.httpclient.auth.AuthState;
   45   import org.apache.commons.httpclient.auth.AuthenticationException;
   46   import org.apache.commons.httpclient.auth.CredentialsProvider;
   47   import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
   48   import org.apache.commons.httpclient.auth.AuthScope;
   49   import org.apache.commons.httpclient.auth.MalformedChallengeException;
   50   import org.apache.commons.httpclient.params.HostParams;
   51   import org.apache.commons.httpclient.params.HttpClientParams;
   52   import org.apache.commons.httpclient.params.HttpConnectionParams;
   53   import org.apache.commons.httpclient.params.HttpMethodParams;
   54   import org.apache.commons.httpclient.params.HttpParams;
   55   import org.apache.commons.logging.Log;
   56   import org.apache.commons.logging.LogFactory;
   57   
   58   /**
   59    * Handles the process of executing a method including authentication, redirection and retries.
   60    * 
   61    * @since 3.0
   62    */
   63   class HttpMethodDirector {
   64   
   65       /** The www authenticate challange header. */
   66       public static final String WWW_AUTH_CHALLENGE = "WWW-Authenticate";
   67   
   68       /** The www authenticate response header. */
   69       public static final String WWW_AUTH_RESP = "Authorization";
   70   
   71       /** The proxy authenticate challange header. */
   72       public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate";
   73   
   74       /** The proxy authenticate response header. */
   75       public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
   76   
   77       private static final Log LOG = LogFactory.getLog(HttpMethodDirector.class);
   78   
   79       private ConnectMethod connectMethod;
   80       
   81       private HttpState state;
   82       
   83       private HostConfiguration hostConfiguration;
   84       
   85       private HttpConnectionManager connectionManager;
   86       
   87       private HttpClientParams params;
   88       
   89       private HttpConnection conn;
   90       
   91       /** A flag to indicate if the connection should be released after the method is executed. */
   92       private boolean releaseConnection = false;
   93   
   94       /** Authentication processor */
   95       private AuthChallengeProcessor authProcessor = null;
   96   
   97       private Set redirectLocations = null; 
   98       
   99       public HttpMethodDirector(
  100           final HttpConnectionManager connectionManager,
  101           final HostConfiguration hostConfiguration,
  102           final HttpClientParams params,
  103           final HttpState state
  104       ) {
  105           super();
  106           this.connectionManager = connectionManager;
  107           this.hostConfiguration = hostConfiguration;
  108           this.params = params;
  109           this.state = state;
  110           this.authProcessor = new AuthChallengeProcessor(this.params);
  111       }
  112       
  113       
  114       /**
  115        * Executes the method associated with this method director.
  116        * 
  117        * @throws IOException
  118        * @throws HttpException
  119        */
  120       public void executeMethod(final HttpMethod method) throws IOException, HttpException {
  121           if (method == null) {
  122               throw new IllegalArgumentException("Method may not be null");
  123           }
  124           // Link all parameter collections to form the hierarchy:
  125           // Global -> HttpClient -> HostConfiguration -> HttpMethod
  126           this.hostConfiguration.getParams().setDefaults(this.params);
  127           method.getParams().setDefaults(this.hostConfiguration.getParams());
  128           
  129           // Generate default request headers
  130           Collection defaults = (Collection)this.hostConfiguration.getParams().
  131               getParameter(HostParams.DEFAULT_HEADERS);
  132           if (defaults != null) {
  133               Iterator i = defaults.iterator();
  134               while (i.hasNext()) {
  135                   method.addRequestHeader((Header)i.next());
  136               }
  137           }
  138           
  139           try {
  140               int maxRedirects = this.params.getIntParameter(HttpClientParams.MAX_REDIRECTS, 100);
  141   
  142               for (int redirectCount = 0;;) {
  143   
  144                   // make sure the connection we have is appropriate
  145                   if (this.conn != null && !hostConfiguration.hostEquals(this.conn)) {
  146                       this.conn.setLocked(false);
  147                       this.conn.releaseConnection();
  148                       this.conn = null;
  149                   }
  150           
  151                   // get a connection, if we need one
  152                   if (this.conn == null) {
  153                       this.conn = connectionManager.getConnectionWithTimeout(
  154                           hostConfiguration,
  155                           this.params.getConnectionManagerTimeout() 
  156                       );
  157                       this.conn.setLocked(true);
  158                       if (this.params.isAuthenticationPreemptive()
  159                        || this.state.isAuthenticationPreemptive()) 
  160                       {
  161                           LOG.debug("Preemptively sending default basic credentials");
  162                           method.getHostAuthState().setPreemptive();
  163                           method.getHostAuthState().setAuthAttempted(true);
  164                           if (this.conn.isProxied() && !this.conn.isSecure()) {
  165                               method.getProxyAuthState().setPreemptive();
  166                               method.getProxyAuthState().setAuthAttempted(true);
  167                           }
  168                       }
  169                   }
  170                   authenticate(method);
  171                   executeWithRetry(method);
  172                   if (this.connectMethod != null) {
  173                       fakeResponse(method);
  174                       break;
  175                   }
  176                   
  177                   boolean retry = false;
  178                   if (isRedirectNeeded(method)) {
  179                       if (processRedirectResponse(method)) {
  180                           retry = true;
  181                           ++redirectCount;
  182                           if (redirectCount >= maxRedirects) {
  183                               LOG.error("Narrowly avoided an infinite loop in execute");
  184                               throw new RedirectException("Maximum redirects ("
  185                                   + maxRedirects + ") exceeded");
  186                           }
  187                           if (LOG.isDebugEnabled()) {
  188                               LOG.debug("Execute redirect " + redirectCount + " of " + maxRedirects);
  189                           }
  190                       }
  191                   }
  192                   if (isAuthenticationNeeded(method)) {
  193                       if (processAuthenticationResponse(method)) {
  194                           LOG.debug("Retry authentication");
  195                           retry = true;
  196                       }
  197                   }
  198                   if (!retry) {
  199                       break;
  200                   }
  201                   // retry - close previous stream.  Caution - this causes
  202                   // responseBodyConsumed to be called, which may also close the
  203                   // connection.
  204                   if (method.getResponseBodyAsStream() != null) {
  205                       method.getResponseBodyAsStream().close();
  206                   }
  207   
  208               } //end of retry loop
  209           } finally {
  210               if (this.conn != null) {
  211                   this.conn.setLocked(false);
  212               }
  213               // If the response has been fully processed, return the connection
  214               // to the pool.  Use this flag, rather than other tests (like
  215               // responseStream == null), as subclasses, might reset the stream,
  216               // for example, reading the entire response into a file and then
  217               // setting the file as the stream.
  218               if (
  219                   (releaseConnection || method.getResponseBodyAsStream() == null) 
  220                   && this.conn != null
  221               ) {
  222                   this.conn.releaseConnection();
  223               }
  224           }
  225   
  226       }
  227   
  228       
  229       private void authenticate(final HttpMethod method) {
  230           try {
  231               if (this.conn.isProxied() && !this.conn.isSecure()) {
  232                   authenticateProxy(method);
  233               }
  234               authenticateHost(method);
  235           } catch (AuthenticationException e) {
  236               LOG.error(e.getMessage(), e);
  237           }
  238       }
  239   
  240   
  241       private boolean cleanAuthHeaders(final HttpMethod method, final String name) {
  242           Header[] authheaders = method.getRequestHeaders(name);
  243           boolean clean = true;
  244           for (int i = 0; i < authheaders.length; i++) {
  245               Header authheader = authheaders[i];
  246               if (authheader.isAutogenerated()) {
  247                   method.removeRequestHeader(authheader);
  248               } else {
  249                   clean = false;
  250               }
  251           }
  252           return clean;
  253       }
  254       
  255   
  256       private void authenticateHost(final HttpMethod method) throws AuthenticationException {
  257           // Clean up existing authentication headers
  258           if (!cleanAuthHeaders(method, WWW_AUTH_RESP)) {
  259               // User defined authentication header(s) present
  260               return;
  261           }
  262           AuthState authstate = method.getHostAuthState();
  263           AuthScheme authscheme = authstate.getAuthScheme();
  264           if (authscheme == null) {
  265               return;
  266           }
  267           if (authstate.isAuthRequested() || !authscheme.isConnectionBased()) {
  268               String host = method.getParams().getVirtualHost();
  269               if (host == null) {
  270                   host = conn.getHost();
  271               }
  272               int port = conn.getPort();
  273               AuthScope authscope = new AuthScope(
  274                   host, port, 
  275                   authscheme.getRealm(), 
  276                   authscheme.getSchemeName());  
  277               if (LOG.isDebugEnabled()) {
  278                   LOG.debug("Authenticating with " + authscope);
  279               }
  280               Credentials credentials = this.state.getCredentials(authscope);
  281               if (credentials != null) {
  282                   String authstring = authscheme.authenticate(credentials, method);
  283                   if (authstring != null) {
  284                       method.addRequestHeader(new Header(WWW_AUTH_RESP, authstring, true));
  285                   }
  286               } else {
  287                   if (LOG.isWarnEnabled()) {
  288                       LOG.warn("Required credentials not available for " + authscope);
  289                       if (method.getHostAuthState().isPreemptive()) {
  290                           LOG.warn("Preemptive authentication requested but no default " +
  291                               "credentials available"); 
  292                       }
  293                   }
  294               }
  295           }
  296       }
  297   
  298   
  299       private void authenticateProxy(final HttpMethod method) throws AuthenticationException {
  300           // Clean up existing authentication headers
  301           if (!cleanAuthHeaders(method, PROXY_AUTH_RESP)) {
  302               // User defined authentication header(s) present
  303               return;
  304           }
  305           AuthState authstate = method.getProxyAuthState();
  306           AuthScheme authscheme = authstate.getAuthScheme();
  307           if (authscheme == null) {
  308               return;
  309           }
  310           if (authstate.isAuthRequested() || !authscheme.isConnectionBased()) {
  311               AuthScope authscope = new AuthScope(
  312                   conn.getProxyHost(), conn.getProxyPort(), 
  313                   authscheme.getRealm(), 
  314                   authscheme.getSchemeName());  
  315               if (LOG.isDebugEnabled()) {
  316                   LOG.debug("Authenticating with " + authscope);
  317               }
  318               Credentials credentials = this.state.getProxyCredentials(authscope);
  319               if (credentials != null) {
  320                   String authstring = authscheme.authenticate(credentials, method);
  321                   if (authstring != null) {
  322                       method.addRequestHeader(new Header(PROXY_AUTH_RESP, authstring, true));
  323                   }
  324               } else {
  325                   if (LOG.isWarnEnabled()) {
  326                       LOG.warn("Required proxy credentials not available for " + authscope);
  327                       if (method.getProxyAuthState().isPreemptive()) {
  328                           LOG.warn("Preemptive authentication requested but no default " +
  329                               "proxy credentials available"); 
  330                       }
  331                   }
  332               }
  333           }
  334       }
  335       
  336       
  337       /**
  338        * Applies connection parameters specified for a given method
  339        * 
  340        * @param method HTTP method
  341        * 
  342        * @throws IOException if an I/O occurs setting connection parameters 
  343        */
  344       private void applyConnectionParams(final HttpMethod method) throws IOException {
  345           int timeout = 0;
  346           // see if a timeout is given for this method
  347           Object param = method.getParams().getParameter(HttpMethodParams.SO_TIMEOUT);
  348           if (param == null) {
  349               // if not, use the default value
  350               param = this.conn.getParams().getParameter(HttpConnectionParams.SO_TIMEOUT);
  351           }
  352           if (param != null) {
  353               timeout = ((Integer)param).intValue();
  354           }
  355           this.conn.setSocketTimeout(timeout);                    
  356       }
  357       
  358       /**
  359        * Executes a method with the current hostConfiguration.
  360        *
  361        * @throws IOException if an I/O (transport) error occurs. Some transport exceptions 
  362        * can be recovered from.
  363        * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
  364        * cannot be recovered from.
  365        */
  366       private void executeWithRetry(final HttpMethod method) 
  367           throws IOException, HttpException {
  368           
  369           /** How many times did this transparently handle a recoverable exception? */
  370           int execCount = 0;
  371           // loop until the method is successfully processed, the retryHandler 
  372           // returns false or a non-recoverable exception is thrown
  373           try {
  374               while (true) {
  375                   execCount++;
  376                   try {
  377   
  378                       if (LOG.isTraceEnabled()) {
  379                           LOG.trace("Attempt number " + execCount + " to process request");
  380                       }
  381                       if (this.conn.getParams().isStaleCheckingEnabled()) {
  382                           this.conn.closeIfStale();
  383                       }
  384                       if (!this.conn.isOpen()) {
  385                           // this connection must be opened before it can be used
  386                           // This has nothing to do with opening a secure tunnel
  387                           this.conn.open();
  388                           if (this.conn.isProxied() && this.conn.isSecure() 
  389                           && !(method instanceof ConnectMethod)) {
  390                               // we need to create a secure tunnel before we can execute the real method
  391                               if (!executeConnect()) {
  392                                   // abort, the connect method failed
  393                                   return;
  394                               }
  395                           }
  396                       }
  397                       applyConnectionParams(method);                    
  398                       method.execute(state, this.conn);
  399                       break;
  400                   } catch (HttpException e) {
  401                       // filter out protocol exceptions which cannot be recovered from
  402                       throw e;
  403                   } catch (IOException e) {
  404                       LOG.debug("Closing the connection.");
  405                       this.conn.close();
  406                       // test if this method should be retried
  407                       // ========================================
  408                       // this code is provided for backward compatibility with 2.0
  409                       // will be removed in the next major release
  410                       if (method instanceof HttpMethodBase) {
  411                           MethodRetryHandler handler = 
  412                               ((HttpMethodBase)method).getMethodRetryHandler();
  413                           if (handler != null) {
  414                               if (!handler.retryMethod(
  415                                       method,
  416                                       this.conn, 
  417                                       new HttpRecoverableException(e.getMessage()),
  418                                       execCount, 
  419                                       method.isRequestSent())) {
  420                                   LOG.debug("Method retry handler returned false. "
  421                                           + "Automatic recovery will not be attempted");
  422                                   throw e;
  423                               }
  424                           }
  425                       }
  426                       // ========================================
  427                       HttpMethodRetryHandler handler = 
  428                           (HttpMethodRetryHandler)method.getParams().getParameter(
  429                                   HttpMethodParams.RETRY_HANDLER);
  430                       if (handler == null) {
  431                           handler = new DefaultHttpMethodRetryHandler();
  432                       }
  433                       if (!handler.retryMethod(method, e, execCount)) {
  434                           LOG.debug("Method retry handler returned false. "
  435                                   + "Automatic recovery will not be attempted");
  436                           throw e;
  437                       }
  438                       if (LOG.isInfoEnabled()) {
  439                           LOG.info("I/O exception ("+ e.getClass().getName() +") caught when processing request: "
  440                                   + e.getMessage());
  441                       }
  442                       if (LOG.isDebugEnabled()) {
  443                           LOG.debug(e.getMessage(), e);
  444                       }
  445                       LOG.info("Retrying request");
  446                   }
  447               }
  448           } catch (IOException e) {
  449               if (this.conn.isOpen()) {
  450                   LOG.debug("Closing the connection.");
  451                   this.conn.close();
  452               }
  453               releaseConnection = true;
  454               throw e;
  455           } catch (RuntimeException e) {
  456               if (this.conn.isOpen()) {
  457                   LOG.debug("Closing the connection.");
  458                   this.conn.close();
  459               }
  460               releaseConnection = true;
  461               throw e;
  462           }
  463       }
  464       
  465       /**
  466        * Executes a ConnectMethod to establish a tunneled connection.
  467        * 
  468        * @return <code>true</code> if the connect was successful
  469        * 
  470        * @throws IOException
  471        * @throws HttpException
  472        */
  473       private boolean executeConnect() 
  474           throws IOException, HttpException {
  475   
  476           this.connectMethod = new ConnectMethod(this.hostConfiguration);
  477           this.connectMethod.getParams().setDefaults(this.hostConfiguration.getParams());
  478           
  479           int code;
  480           for (;;) {
  481               if (!this.conn.isOpen()) {
  482                   this.conn.open();
  483               }
  484               if (this.params.isAuthenticationPreemptive()
  485                       || this.state.isAuthenticationPreemptive()) {
  486                   LOG.debug("Preemptively sending default basic credentials");
  487                   this.connectMethod.getProxyAuthState().setPreemptive();
  488                   this.connectMethod.getProxyAuthState().setAuthAttempted(true);
  489               }
  490               try {
  491                   authenticateProxy(this.connectMethod);
  492               } catch (AuthenticationException e) {
  493                   LOG.error(e.getMessage(), e);
  494               }
  495               applyConnectionParams(this.connectMethod);                    
  496               this.connectMethod.execute(state, this.conn);
  497               code = this.connectMethod.getStatusCode();
  498               boolean retry = false;
  499               AuthState authstate = this.connectMethod.getProxyAuthState(); 
  500               authstate.setAuthRequested(code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
  501               if (authstate.isAuthRequested()) {
  502                   if (processAuthenticationResponse(this.connectMethod)) {
  503                       retry = true;
  504                   }
  505               }
  506               if (!retry) {
  507                   break;
  508               }
  509               if (this.connectMethod.getResponseBodyAsStream() != null) {
  510                   this.connectMethod.getResponseBodyAsStream().close();
  511               }
  512           }
  513           if ((code >= 200) && (code < 300)) {
  514               this.conn.tunnelCreated();
  515               // Drop the connect method, as it is no longer needed
  516               this.connectMethod = null;
  517               return true;
  518           } else {
  519               this.conn.close();
  520               return false;
  521           }
  522       }
  523   
  524       /**
  525        * Fake response
  526        * @param method
  527        * @return
  528        */
  529       
  530       private void fakeResponse(final HttpMethod method)
  531           throws IOException, HttpException {
  532           // What is to follow is an ugly hack.
  533           // I REALLY hate having to resort to such
  534           // an appalling trick
  535           // The only feasible solution is to split monolithic
  536           // HttpMethod into HttpRequest/HttpResponse pair.
  537           // That would allow to execute CONNECT method 
  538           // behind the scene and return CONNECT HttpResponse 
  539           // object in response to the original request that 
  540           // contains the correct status line, headers & 
  541           // response body.
  542           LOG.debug("CONNECT failed, fake the response for the original method");
  543           // Pass the status, headers and response stream to the wrapped
  544           // method.
  545           // To ensure that the connection is not released more than once
  546           // this method is still responsible for releasing the connection. 
  547           // This will happen when the response body is consumed, or when
  548           // the wrapped method closes the response connection in 
  549           // releaseConnection().
  550           if (method instanceof HttpMethodBase) {
  551               ((HttpMethodBase) method).fakeResponse(
  552                   this.connectMethod.getStatusLine(),
  553                   this.connectMethod.getResponseHeaderGroup(),
  554                   this.connectMethod.getResponseBodyAsStream()
  555               );
  556               method.getProxyAuthState().setAuthScheme(
  557                   this.connectMethod.getProxyAuthState().getAuthScheme());
  558               this.connectMethod = null;
  559           } else {
  560               releaseConnection = true;
  561               LOG.warn(
  562                   "Unable to fake response on method as it is not derived from HttpMethodBase.");
  563           }
  564       }
  565       
  566       /**
  567        * Process the redirect response.
  568        * 
  569        * @return <code>true</code> if the redirect was successful
  570        */
  571       private boolean processRedirectResponse(final HttpMethod method)
  572        throws RedirectException {
  573           //get the location header to find out where to redirect to
  574           Header locationHeader = method.getResponseHeader("location");
  575           if (locationHeader == null) {
  576               // got a redirect response, but no location header
  577               LOG.error("Received redirect response " + method.getStatusCode()
  578                       + " but no location header");
  579               return false;
  580           }
  581           String location = locationHeader.getValue();
  582           if (LOG.isDebugEnabled()) {
  583               LOG.debug("Redirect requested to location '" + location + "'");
  584           }
  585           
  586           //rfc2616 demands the location value be a complete URI
  587           //Location       = "Location" ":" absoluteURI
  588           URI redirectUri = null;
  589           URI currentUri = null;
  590   
  591           try {
  592               currentUri = new URI(
  593                   this.conn.getProtocol().getScheme(),
  594                   null,
  595                   this.conn.getHost(), 
  596                   this.conn.getPort(), 
  597                   method.getPath()
  598               );
  599               
  600               String charset = method.getParams().getUriCharset();
  601               redirectUri = new URI(location, true, charset);
  602               
  603               if (redirectUri.isRelativeURI()) {
  604                   if (this.params.isParameterTrue(HttpClientParams.REJECT_RELATIVE_REDIRECT)) {
  605                       LOG.warn("Relative redirect location '" + location + "' not allowed");
  606                       return false;
  607                   } else { 
  608                       //location is incomplete, use current values for defaults
  609                       LOG.debug("Redirect URI is not absolute - parsing as relative");
  610                       redirectUri = new URI(currentUri, redirectUri);
  611                   }
  612               } else {
  613                   // Reset the default params
  614                   method.getParams().setDefaults(this.params);
  615               }
  616               method.setURI(redirectUri);
  617               hostConfiguration.setHost(redirectUri);
  618           } catch (URIException ex) {
  619               throw new InvalidRedirectLocationException(
  620                       "Invalid redirect location: " + location, location, ex);
  621           }
  622   
  623           if (this.params.isParameterFalse(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS)) {
  624               if (this.redirectLocations == null) {
  625                   this.redirectLocations = new HashSet();
  626               }
  627               this.redirectLocations.add(currentUri);
  628               try {
  629                   if(redirectUri.hasQuery()) {
  630                       redirectUri.setQuery(null);
  631                   }
  632               } catch (URIException e) {
  633                   // Should never happen
  634                   return false;
  635               }
  636   
  637               if (this.redirectLocations.contains(redirectUri)) {
  638                   throw new CircularRedirectException("Circular redirect to '" +
  639                       redirectUri + "'");
  640               }
  641           }
  642   
  643           if (LOG.isDebugEnabled()) {
  644               LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
  645                   + "' to '" + redirectUri.getEscapedURI());
  646           }
  647           //And finally invalidate the actual authentication scheme
  648           method.getHostAuthState().invalidate(); 
  649           return true;
  650       }
  651   
  652       /**
  653        * Processes a response that requires authentication
  654        *
  655        * @param method the current {@link HttpMethod HTTP method}
  656        *
  657        * @return <tt>true</tt> if the authentication challenge can be responsed to,
  658        *   (that is, at least one of the requested authentication scheme is supported, 
  659        *   and matching credentials have been found), <tt>false</tt> otherwise.
  660        */
  661       private boolean processAuthenticationResponse(final HttpMethod method) {
  662           LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
  663               + "HttpState, HttpConnection)");
  664   
  665           try {
  666               switch (method.getStatusCode()) {
  667                   case HttpStatus.SC_UNAUTHORIZED:
  668                       return processWWWAuthChallenge(method);
  669                   case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
  670                       return processProxyAuthChallenge(method);
  671                   default:
  672                       return false;
  673               }
  674           } catch (Exception e) {
  675               if (LOG.isErrorEnabled()) {
  676                   LOG.error(e.getMessage(), e);
  677               }
  678               return false;
  679           }
  680       }
  681   
  682       private boolean processWWWAuthChallenge(final HttpMethod method)
  683           throws MalformedChallengeException, AuthenticationException  
  684       {
  685           AuthState authstate = method.getHostAuthState();
  686           Map challenges = AuthChallengeParser.parseChallenges(
  687               method.getResponseHeaders(WWW_AUTH_CHALLENGE));
  688           if (challenges.isEmpty()) {
  689               LOG.debug("Authentication challenge(s) not found");
  690               return false; 
  691           }
  692           AuthScheme authscheme = null;
  693           try {
  694               authscheme = this.authProcessor.processChallenge(authstate, challenges);
  695           } catch (AuthChallengeException e) {
  696               if (LOG.isWarnEnabled()) {
  697                   LOG.warn(e.getMessage());
  698               }
  699           }
  700           if (authscheme == null) {
  701               return false;
  702           }
  703           String host = method.getParams().getVirtualHost();
  704           if (host == null) {
  705               host = conn.getHost();
  706           }
  707           int port = conn.getPort();
  708           AuthScope authscope = new AuthScope(
  709               host, port, 
  710               authscheme.getRealm(), 
  711               authscheme.getSchemeName());
  712           
  713           if (LOG.isDebugEnabled()) {
  714               LOG.debug("Authentication scope: " + authscope);
  715           }
  716           if (authstate.isAuthAttempted() && authscheme.isComplete()) {
  717               // Already tried and failed
  718               Credentials credentials = promptForCredentials(
  719                   authscheme, method.getParams(), authscope);
  720               if (credentials == null) {
  721                   if (LOG.isInfoEnabled()) {
  722                       LOG.info("Failure authenticating with " + authscope);
  723                   }
  724                   return false;
  725               } else {
  726                   return true;
  727               }
  728           } else {
  729               authstate.setAuthAttempted(true);
  730               Credentials credentials = this.state.getCredentials(authscope);
  731               if (credentials == null) {
  732                   credentials = promptForCredentials(
  733                       authscheme, method.getParams(), authscope);
  734               }
  735               if (credentials == null) {
  736                   if (LOG.isInfoEnabled()) {
  737                       LOG.info("No credentials available for " + authscope); 
  738                   }
  739                   return false;
  740               } else {
  741                   return true;
  742               }
  743           }
  744       }
  745   
  746       private boolean processProxyAuthChallenge(final HttpMethod method)
  747           throws MalformedChallengeException, AuthenticationException
  748       {  
  749           AuthState authstate = method.getProxyAuthState();
  750           Map proxyChallenges = AuthChallengeParser.parseChallenges(
  751               method.getResponseHeaders(PROXY_AUTH_CHALLENGE));
  752           if (proxyChallenges.isEmpty()) {
  753               LOG.debug("Proxy authentication challenge(s) not found");
  754               return false; 
  755           }
  756           AuthScheme authscheme = null;
  757           try {
  758               authscheme = this.authProcessor.processChallenge(authstate, proxyChallenges);
  759           } catch (AuthChallengeException e) {
  760               if (LOG.isWarnEnabled()) {
  761                   LOG.warn(e.getMessage());
  762               }
  763           }
  764           if (authscheme == null) {
  765               return false;
  766           }
  767           AuthScope authscope = new AuthScope(
  768               conn.getProxyHost(), conn.getProxyPort(), 
  769               authscheme.getRealm(), 
  770               authscheme.getSchemeName());  
  771   
  772           if (LOG.isDebugEnabled()) {
  773               LOG.debug("Proxy authentication scope: " + authscope);
  774           }
  775           if (authstate.isAuthAttempted() && authscheme.isComplete()) {
  776               // Already tried and failed
  777               Credentials credentials = promptForProxyCredentials(
  778                   authscheme, method.getParams(), authscope);
  779               if (credentials == null) {
  780                   if (LOG.isInfoEnabled()) {
  781                       LOG.info("Failure authenticating with " + authscope);
  782                   }
  783                   return false;
  784               } else {
  785                   return true;
  786               }
  787           } else {
  788               authstate.setAuthAttempted(true);
  789               Credentials credentials = this.state.getProxyCredentials(authscope);
  790               if (credentials == null) {
  791                   credentials = promptForProxyCredentials(
  792                       authscheme, method.getParams(), authscope);
  793               }
  794               if (credentials == null) {
  795                   if (LOG.isInfoEnabled()) {
  796                       LOG.info("No credentials available for " + authscope); 
  797                   }
  798                   return false;
  799               } else {
  800                   return true;
  801               }
  802           }
  803       }
  804   
  805       /**
  806        * Tests if the {@link HttpMethod method} requires a redirect to another location.
  807        * 
  808        * @param method HTTP method
  809        * 
  810        * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
  811        */
  812       private boolean isRedirectNeeded(final HttpMethod method) {
  813           switch (method.getStatusCode()) {
  814               case HttpStatus.SC_MOVED_TEMPORARILY:
  815               case HttpStatus.SC_MOVED_PERMANENTLY:
  816               case HttpStatus.SC_SEE_OTHER:
  817               case HttpStatus.SC_TEMPORARY_REDIRECT:
  818                   LOG.debug("Redirect required");
  819                   if (method.getFollowRedirects()) {
  820                       return true;
  821                   } else {
  822                       return false;
  823                   }
  824               default:
  825                   return false;
  826           } //end of switch
  827       }
  828   
  829       /**
  830        * Tests if the {@link HttpMethod method} requires authentication.
  831        * 
  832        * @param method HTTP method
  833        * 
  834        * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
  835        */
  836       private boolean isAuthenticationNeeded(final HttpMethod method) {
  837           method.getHostAuthState().setAuthRequested(
  838                   method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED);
  839           method.getProxyAuthState().setAuthRequested(
  840                   method.getStatusCode() == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
  841           if (method.getHostAuthState().isAuthRequested() || 
  842               method.getProxyAuthState().isAuthRequested()) {
  843               LOG.debug("Authorization required");
  844               if (method.getDoAuthentication()) { //process authentication response
  845                   return true;
  846               } else { //let the client handle the authenticaiton
  847                   LOG.info("Authentication requested but doAuthentication is "
  848                           + "disabled");
  849                   return false;
  850               }
  851           } else {
  852               return false;
  853           }
  854       }
  855   
  856       private Credentials promptForCredentials(
  857           final AuthScheme authScheme,
  858           final HttpParams params, 
  859           final AuthScope authscope)
  860       {
  861           LOG.debug("Credentials required");
  862           Credentials creds = null;
  863           CredentialsProvider credProvider = 
  864               (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
  865           if (credProvider != null) {
  866               try {
  867                   creds = credProvider.getCredentials(
  868                       authScheme, authscope.getHost(), authscope.getPort(), false);
  869               } catch (CredentialsNotAvailableException e) {
  870                   LOG.warn(e.getMessage());
  871               }
  872               if (creds != null) {
  873                   this.state.setCredentials(authscope, creds);
  874                   if (LOG.isDebugEnabled()) {
  875                       LOG.debug(authscope + " new credentials given");
  876                   }
  877               }
  878           } else {
  879               LOG.debug("Credentials provider not available");
  880           }
  881           return creds;
  882       }
  883   
  884       private Credentials promptForProxyCredentials(
  885           final AuthScheme authScheme,
  886           final HttpParams params,
  887           final AuthScope authscope) 
  888       {
  889           LOG.debug("Proxy credentials required");
  890           Credentials creds = null;
  891           CredentialsProvider credProvider = 
  892               (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
  893           if (credProvider != null) {
  894               try {
  895                   creds = credProvider.getCredentials(
  896                       authScheme, authscope.getHost(), authscope.getPort(), true);
  897               } catch (CredentialsNotAvailableException e) {
  898                   LOG.warn(e.getMessage());
  899               }
  900               if (creds != null) {
  901                   this.state.setProxyCredentials(authscope, creds);
  902                   if (LOG.isDebugEnabled()) {
  903                       LOG.debug(authscope + " new credentials given");
  904                   }
  905               }
  906           } else {
  907               LOG.debug("Proxy credentials provider not available");
  908           }
  909           return creds;
  910       }
  911   
  912       /**
  913        * @return
  914        */
  915       public HostConfiguration getHostConfiguration() {
  916           return hostConfiguration;
  917       }
  918   
  919       /**
  920        * @return
  921        */
  922       public HttpState getState() {
  923           return state;
  924       }
  925   
  926       /**
  927        * @return
  928        */
  929       public HttpConnectionManager getConnectionManager() {
  930           return connectionManager;
  931       }
  932   
  933       /**
  934        * @return
  935        */
  936       public HttpParams getParams() {
  937           return this.params;
  938       }
  939   }

Save This Page
Home » commons-httpclient-3.1-src » org.apache.commons » httpclient » [javadoc | source]